标签:
本文对部分算法有参考其他博客,在这里注明,此为初稿,以后会不断修改!
/*****************************************************************/
/* 冒泡排序时间复杂度最好的情况为O(n),最坏的情况是O(n^2)
* 基本思想是:两两比较相邻记录的关键字,如果反序则交换 */
void BubbleSort1(int arr[], int num)
{
int i, j;
for (i = 0; i < num; i++)
{
for (j = 1; j < num - i; j++)
{
if (arr[j - 1] > arr[j])
swap1(&arr[j - 1], &arr[j]);
}
}
}
// 改进思路:设置标志位,明显如果有一趟没有发生交换(flag = flase),说明排序已经完成.
void BubbleSort2(int arr[], int num)
{
int k = num;
int j;
bool flag = true;
while (flag)
{
flag = false;
for (j = 1; j < k; j++)
{
if (arr[j - 1] > arr[j])
{
swap1(&arr[j - 1], &arr[j]);
flag = true;
}
}
k--;
}
}
//改进思路:记录一轮下来标记的最后位置,下次从头部遍历到这个位置就Ok
void BubbleSort3(int arr[], int num)
{
int k, j;
int flag = num;
while (flag > 0)
{
k = flag;
flag = 0;
for (j = 1; j < k; j++)
{
if (arr[j - 1] > arr[j])
{
swap1(&arr[j - 1], &arr[j]);
flag = j;
}
}
}
}
/*************************************************************************/
/**************************************************************************/
/*插入排序: 将一个记录插入到已经排好序的有序表中, 从而得到一个新的,记录数增1的有序表
* 时间复杂度也为O(n^2), 比冒泡法和选择排序的性能要更好一些 */
void InsertionSort(int arr[], int num)
{
int temp;
int i, j;
for (i = 1; i < num; i++)
{
temp = arr[i];
for (j = i; j > 0 && arr[j - 1] > temp; j--)
arr[j] = arr[j - 1];
arr[j] = temp;
}
}
/**************************************************************************/
/* 简单选择排序(simple selection sort) 就是通过n-i次关键字之间的比较,从n-i+1
* 个记录中选择关键字最小的记录,并和第i(1<=i<=n)个记录交换之
* 尽管与冒泡排序同为O(n^2),但简单选择排序的性能要略优于冒泡排序 */ 不稳定
void SelectSort(int arr[], int num)
{
int i, j, Mindex;
for (i = 0; i < num; i++)
{
Mindex = i;
for (j = i + 1; j < num; j++)
{
if (arr[j] < arr[Mindex])
Mindex = j;
}
swap1(&arr[i], &arr[Mindex]);
}
}
希尔排序:
void shellsort1(int a[], int n)
{
int i, j, gap;
for (gap = n / 2; gap > 0; gap /= 2) //步长
for (i = 0; i < gap; i++) //直接插入排序
{
for (j = i + gap; j < n; j += gap) //依次插入每个组内的数,它们的位置为i,i+gap,i+2gap```
if (a[j] < a[j - gap])
{
int temp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > temp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = temp;
}
}
}
堆排序:
void maxHeap(int *a,int n,int i) //堆调整
{
//left、right、largest分别指向
//左孩子、右孩子、{a[i],a[left]}中最大的一个
int left,right,largest;
largest=left=2*i;
if(left>n)
return;
right=2*i+1;
if(right<=n && a[right]>a[left]){
largest=right;
}
if(a[i]<a[largest]){//根结点的值不是最大时,交换a[i],a[largest]
a[i]=a[i]+a[largest];
a[largest]=a[i]-a[largest];
a[i]=a[i]-a[largest];
//自上而下调整堆
maxHeap(a,n,largest);
}
}
void creatHeap(int *a,int n) //建堆
{
int i;
//自下而上调整堆
for(i=n/2;i>=1;i--)
maxHeap(a,n,i);
}
void heapSort(int *a,int n)
{
int i;
creatHeap(a,n);//建堆
for(i=n;i>=2;i--){
//堆顶记录和最后一个记录交换
a[1]=a[1]+a[i];
a[i]=a[1]-a[i];
a[1]=a[1]-a[i];
//堆中记录个数减少一个,筛选法调整堆
maxHeap(a,i-1,1);
}
}
归并排序:
public void Merger(int[] v, int first, int mid, int last)
{
Queue<int> tempV = new Queue<int>();
int indexA, indexB;
//设置indexA,并扫描subArray1 [first,mid]
//设置indexB,并扫描subArray2 [mid,last]
indexA = first;
indexB = mid;
//在没有比较完两个子标的情况下,比较 v[indexA]和v[indexB]
//将其中小的放到临时变量tempV中
while (indexA < mid && indexB < last)
{
if (v[indexA] < v[indexB])
{
tempV.Enqueue(v[indexA]);
indexA++;
}
else
{
tempV.Enqueue(v[indexB]);
indexB++;
}
}
//复制没有比较完子表中的元素
while (indexA < mid)
{
tempV.Enqueue(v[indexA]);
indexA++;
}
while (indexB < last)
{
tempV.Enqueue(v[indexB]);
indexB++;
}
int index = 0;
while (tempV.Count > 0)
{
v[first+index] = tempV.Dequeue();
index++;
}
}
public void MergerSort(int[] v, int first, int last)
{
if (first + 1 < last)
{
int mid = (first + last) / 2;
MergerSort(v, first, mid);
MergerSort(v, mid, last);
Merger(v, first, mid, last);
}
}
快速:
单次划分时设头元素为p,将p放到最终的正确位置,设置划分边界并扫描所有元素将划分边界放到正确的位置
递归排序左右两边
def partition(start,end,s):
if end-start<1:
return
edge=start+1
for p in range(start+1, end+1):
if s[p]<=s[start]:
tmp=s[p]
s[p]=s[edge]
s[edge]=tmp
edge+=1
tmp=s[edge-1]
s[edge-1]=s[start]
s[start]=tmp
return edge-1
def quickSort2(start,end ,s):
if end-start<1:
return
k=partition(start,end ,s)
quickSort2(start,k-1,s)
quickSort2(k+1,end,s)
def quickSort(s):
length=len(s)
if length<=1:
return s
quickSort2(0,length-1,s)
return s
计数:时间复杂度:O(n+k),空间复杂度:n+O(k)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//针对c数组的大小,优化过的计数排序 publicclassCountSort{ publicstaticvoidmain(String[]args){ //排序的数组 int a[]={100,93,97,92,96,99,92,89,93,97,90,94,92,95}; int b[]=countSort(a); for(inti:b){ System.out.print(i+""); } System.out.println(); } public static int[] countSort(int[]a){ int b[] = new int[a.length]; int max = a[0],min = a[0]; for(int i:a){ if(i>max){ max=i; } if(i<min){ min=i; } }//这里k为待排序数组中数值的范围 int k=max-min+1; int c[]=new int[k]; for(int i=0;i<a.length;++i){ c[a[i]-min]+=1;//c为待排序数组相对值的统计 } for(int i=1;i<c.length;++i){ c[i]=c[i]+c[i-1];//将c[i]由相对值个数转变为比该相对值小于等于的个数 } for(int i=a.length-1;i>=0;--i){//保证稳定性 b[--c[a[i]-min]]=a[i];//根据小于等于a[i]的数值个数安排a[i]的位置 } return b; } } |
基数排序:
/******************** 基数排序LSD*********************/
void base_sort_ISD(int *arr, int num)
{
Node *buck[10]; // 创建一个链表数组
Node *tail[10]; //保存每条链表尾节点指针集合,
//这样插入buck数组时就不用每次遍历到末尾
int i, MaxValue, kth, high, low;
Node *ptr;
for(MaxValue = arr[0], i = 1; i < num; i++)
MaxValue = max(MaxValue, arr[i]);
memset(buck, 0, sizeof(buck));
memset(tail, 0, sizeof(buck));
for(low = 1; high = low * 10, low < MaxValue; low *= 10)
{
//只要没排好序就一直排序
for(i = 0; i < num; i++)
{
//往桶里放
kth = (arr[i] % high) / low;//取出数据的某一位,作为桶的索引
ptr = new Node(arr[i]); //创建新节点
//接到末尾
if (buck[kth] != NULL)
{
tail[kth]->next_ = ptr;
tail[kth] = ptr;
}
else
{
buck[kth] = ptr;
tail[kth] = ptr;
}
}
//把桶中的数据放回数组中(同条链表是从头到尾)
for (kth = 0, i = 0; kth < num; i++)
{
while (buck[i] != NULL)
{
arr[kth++] = buck[i]->key_;
ptr = buck[i];
buck[i] = buck[i]->next_;
delete ptr;
}
}
memset(tail, 0, sizeof(buck));
}
}
桶排序的基本思想
假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]....B[M]中的全部内容即是一个有序序列。
[桶—关键字]映射函数
bindex=f(key) 其中,bindex 为桶数组B的下标(即第bindex个桶), k为待排序列的关键字。桶排序之所以能够高效,其关键在于这个映射函数,它必须做到:如果关键字k1<k2,那么f(k1)<=f(k2)。也就是说B(i)中的最小数据都要大于B(i-1)中最大数据。很显然,映射函数的确定与数据本身的特点有很大的关系,我们下面举个例子:
假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序后得到如下图所示:
对上图只要顺序输出每个B[i]中的数据就可以得到有序序列了。
桶排序代价分析
桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。
对N个关键字进行桶排序的时间复杂度分为两个部分:
(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。
(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。
很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:
(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。
(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。
对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。
总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。
其实我个人还有一个感受:在查找算法中,基于比较的查找算法最好的时间复杂度也是O(logN)。比如折半查找、平衡二叉树、红黑树等。但是Hash表却有O(C)线性级别的查找效率(不冲突情况下查找效率达到O(1))。大家好好体会一下:Hash表的思想和桶排序是不是有一曲同工之妙呢?
源代码
Cpp代码
42. }
44. void main(){
48. }
上面源代码的桶内数据排序,我们使用了基于单链表的直接插入排序算法。可以使用基于双向链表的快排算法提高效率。
标签:
原文地址:http://www.cnblogs.com/littlebugfish/p/4321697.html