排序算法大总结

来源:互联网 发布:济宁seo 编辑:程序博客网 时间:2024/05/16 08:20

数据结构绝对是重中之重!

排序算法主要有9种


冒泡排序  交换排序  选择排序  插入排序  快速排序  归并排序  堆排序  基排序  希尔排序


每个排序都有自己的适宜场合,有不同的时间复杂度和空间复杂度。

时间复杂度主要是从比较次数移动次数来看

接下来一一介绍



一、冒泡排序

算法思想:

1.待排序的数据看作气泡,通过n-1趟比较,从而使较小的数据浮在上层(前),较大的数据沉在下层(后)。

2.每一趟比较,从下层开始比较两个相邻的数据,如果上层的数据大于下层的数据,则交换;否则,不移动顺序继续往上比较。

void BubbleSort(int n,List &A){for (int i=0;i<n;i++){for (int j=n-1;j>=i+1;j--){if (A[j].key<A[j-1].key){swap(A[j],A[j-1]);}}}}

 比较次数移动次数  最好情况n-10  最坏情况n(n-1)/23n(n-1)/2  



平均时间复杂度最差时间复杂度空间复杂度O(N*N)O(N*N)O(1)

二、快速排序

算法思想:(分治算法)

1.找基准元素。FindPivot(int i,int j)

   作用是,使数列左侧都比基准元素要小,右侧都比基准元素要大。

   如何选取?从左侧开始查找两个不同数值中的较大值作为基准元素。如果找不到两个不相同的元素,则返回0。排序结束。例如 3 3 2 5 6 1 0 则选取3为基准元素。

2.划分

   (1)l表示从左边开始遍历的下标,r表示从右边开始遍历的下标。l为指向数列中从左边开始第一个比基准元素大的值;r为数列中从右边开始第一个比基准元素小的值。

   (2)如果l<r,则swap(A[l],A[r]);重复(1)

      如果l>r,也即l=r+1。数列从l-1和l之间划分成两部分。

3.对于划分后的两个数列,继续采用快排算法。

int FindPivot(int i,int j,List &A){//选取基准元素,从数列左侧查找两个不同数值中的较大值为基准元素for (int k=i+1;k<=j;k++){if (A[k].key>A[i].key)return k;else if (A[k].key<A[i].key) return i;}return 0;}int Partition(int i,int j,List &A,int Pivot){//划分数列int l,r;do {for(l=i;A[l].key<Pivot;l++);for(r=j;A[r].key>=Pivot;r--);if (l<r) {swap(A[l],A[r]);}} while(l<=r);return l;}void QuickSort(int i,int j,List &A){int k;int pivot;int pivotindex;pivotindex=FindPivot(i,j,A);if(pivotindex!=0){//递归结束条件pivot=A[pivotindex].key;k=Partition(i,j,A,pivot);QuickSort(i,k-1,A);QuickSort(k,j,A);}}
不过这里有点小错误啦~

就是结束递归结束条件设置的是pivotindex!=0,但是确实第一次可能pivotindex=0,如果结束,就没有进行排序。



最好情况

每次划分后,划分点左侧和右侧长度相同,则时间复杂度为O(nlogn)

T(n)<=2T(n/2)+n
      <=2(2T(n/4)+n/2)+n

      <=4(2T(n/8)+n/4)+2n

      ......

     <=nT(1)+nlogn=O(nlogn)

最坏情况

每次划分只得到一个比上一次划分少一个记录的子序列

平均时间复杂度最差时间复杂度空间复杂度O(nlogn)O(N*N)O(logn)

三、选择排序(selectsort)

算法思想:

与冒泡排序类似,冒泡排序,是找到比前面元素小的元素就交换。而选择排序则是在序列中找到最小的元素,然后与最前面的元素互换

void SelectSort(int n,List &A){int low;int lowindex;for (int i=0;i<n;i++)//在数列中选择最小的值,与A[i]交换{lowindex=i;low=A[i].key;for (int j=i+1;j<n;j++)//找数列中的最小值{if (A[j].key<low){lowindex=j;low=A[j].key;}}swap(A[i],A[lowindex]);}}
性能分析
1.移动次数
  最好情况 0次
  最坏情况 3(n-1)
2.比较次数
   (n-1)+(n-2)+(n-3)+...+2+1=n(n-1)/2=O(n2)
3.时间复杂度 O(n2)
4.空间复杂度 O(1) (需要一个low 和lowindex的空间)


四、堆排序

算法思想:

数列->完全二叉树->初始建堆->整理堆

1.将数列按照完全二叉树的下标排成完全二叉树。

2.对该完全二叉树用PushDown算法依次对n/2,n/2-1,n/2-2,...1建堆。

3.取堆顶元素一定是最小值。

4.交换第N个和第1个元素。重复3。知道取遍所有元素。


首先必须明确一点,二叉树有完整的排序规则,从1开始到n.若某个节点为i,左孩子为2*i,右孩子为2*i+1.


void PushDown(int first,int last,List &A){//整理堆算法int r=first;while (r<=last/2)//如果r<=last/2,则为堆。否则都为叶子节点{if (r==last/2 && last%2==0)//r节点只有左节点{if (A[r].key>A[last].key){swap(A[r],A[last]);    }r=last;}else if (A[r].key>A[2*r].key && A[2*r].key<=A[2*r+1].key)//r节点有左右孩子,左孩子小于根节点,左孩子小于右节点,则将根节点和左孩子交换{swap(A[r],A[2*r]);r*=2;}else if (A[2*r+1].key<A[2*r].key && A[2*r+1].key<A[r].key)//r节点有左右孩子,右孩子小于根节点,右孩子小于左节点,则将根节点和右孩子交换{swap(A[r],A[2*r+1]);r=2*r+1;}else//正常堆无需调整r=last;}}void HeapSort(int n,List &A){//堆排序int i;for (i=n/2;i>=1;i--)PushDown(i,n,A);for (i=n;i>=2;i--){ swap(A[1],A[i]);PushDown(1,i-1,A);}}
对于堆排序一样,输入和输出都从1开始而非0开始,
for (int i=1;i<=n;i++)
{
printf("%d\n",A[i].key);
}
堆排序的时间复杂度为O(nlogn)
五、直接插入排序

算法思想:

每次将待排序的数值插入有序区的相应位置。

什么才是相应位置,在该位置前面找不到元素比他大,则这个位置就是正确的相应位置。

对于直接插入排序,和堆排序一样,输入和输出都从1开始而非0开始,具体原因是因为第一位被哨兵占用。
for (int i=1;i<=n;i++)
{
printf("%d\n",A[i].key);
}

void InsertSort(int size,List &A){A[0].key=-100000;//哨兵for (int i=1;i<=size;i++){int j;j=i;//A[j]为待排序的值,在数列中i之前的为有序区//将j插入正确的位置while (A[j-1].key>A[j].key)//如果j之前的数值比A[j]大,则将该数值与A[j]交换{swap(A[j-1],A[j]);j=j-1;}}}


性能分析
1. 最好情况(正序)
   比较次数:n-1
   移动次数 2(n-1)
   时间复杂度 O(n)
2. 最坏情况 (反序)
   比较次数  2+3+4+...+n
   移动次数  3+4+5+...+n+1
   时间复杂度 O(n2)
3.平均情况
   比较次数 (2+3+4+...+n)/2
   移动次数 (3+4+5+...+n+1)/2
   时间复杂度 O(n2)
4.空间复杂度 O(1),适用于基本有序,或者,尺寸很小的数列

六、归并排序

算法思想:

1.第一遍归并是一一归并。第二遍是二二归并,第三遍是四四归并。第n遍就是2的N次方。

2.归并是两个列表的归并,指针i,j分别指向A中的两段{p...q-1}{q...r}.比较A[i],A[j]的大小,将小者赋给B[k]。如果两段序列不一样长,则将剩下的直接赋给B[].【Merge(int p,int q,int r,List A,List B)】

3.每一遍归并,设当前归并长度为h,,从序列开头开始,尽量归并2个h的序列,直达归并不了,剩下的序列,如果大于h小于2h,则归并两段,如果小于h,则直接复制到归并后的序列。【MergePass(int n,int h,List A,List B)】

4.归并排序【MergeSort(int n,List &A)】,当前归并序列长度大于n,终止排序。若小于n,开始一趟排序。

/****************归并排序*********************/void Merge(int p,int q,int r,List A,List B)//A中两个序列【p...q-1】【q...r】合并到B{int i,j,k;i=p;j=q+1;k=p;while (i<=q&&j<=r){if (A[i].key <= A[j].key)B[k++]=A[i++];elseB[k++]=A[j++];}while (i<=q){B[k++]=A[i++];}while (j<=r){B[k++]=A[j++];}}void MergePass(int n,int h,List A,List B){//把A中长度为h的相邻序列归并为长度为2hint i;for (i=1;i+2*h-1<=n;i+=2*h)//归并长度为h的两个子序列{Merge(i,i+h-1,i+2*h-1,A,B);}if (i+h-1<n)//还有两个子序列,但第二个序列长度小于h{Merge(i,i+h-1,n,A,B);}else{//仅剩一个子序列for (int t=i;t<=n;t++){B[t]=A[i];}}}void MergeSort(int n,List &A){int h=1;List B;if (h<n)//当被归并子序列的长度小于总长度{MergePass(n,h,A,B);//将A中归并到B中h*=2;//归并长度加倍MergePass(n,h,B,A);//将B中归并到A中        h*=2;//归并长度加倍}}

性能分析
1.时间复杂度 O(nlogn)
  一趟归并将A[1...N]将两个长度为h的相邻子序列归并到B[1...N]中,时间复杂度为O(n)。
  共需要logn趟排序。 则归并排序时间复杂度为 O(n)*logn=O(nlogn)
2.空间复杂度 O(n)
  需要长度与A相同的的序列B。则空间复杂度 O(n)

另一种是折半归并,这样就不用了一趟一趟递增归并长度h的归并,
1.两序列分解,A[LOW...HIGH]一分为二,mid=LOW+HIGH
2.递归的对序列A[LOW]...A[MID]和A[MID+1]...A[HIGH]进行归并排序
3.将两个已排序子序列归并为一个有序序列。

void MergeSort_q(int low,int high,List &A,List &B){//折半归并int mid=(low+high)/2;//两序列分解,A[LOW...HIGH]一分为二,mid=LOW+HIGHif (low<high){MergeSort_q(low,mid,A,B);//递归的对序列A[LOW]...A[MID]和A[MID+1]...A[HIGH]进行归并排序MergeSort_q(mid+1,high,A,B);Merge(low,mid,high,A,B);//将两个已排序子序列归并为一个有序序列}}
void main(){List B;input_1();MergeSort_q(1,size,L,B);output_1(size,B);}


根据比较关键字的大小排序,复杂度下限为O(nlogn)

而基数排序不是根据比较关键字的大小,而是根据构成关键字的每个分量的值,比较大小,从而排列记录。
关键字的各分量的取值范围必须为有限

七、基数排序

算法思想:

1.



(未完待续)







0 0