最近由于长时间没写过基本的排序算法,结果导致只知道大概思想便不知怎么去编写这些算法的代码了,所以借着一下午的时间把基本的几个排序算法的代码写了一边,算是对它的复习吧!
一 .「冒泡排序」:
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,
基本的做法如下:
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。代码如下:
void Bubble_Sort(int *array,int count){
int i=count,j=0,temp=0;
while(i>0){
for(j=0;j<i-1;j++){
if(array[j]>array[j+1]){
temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
i--;
}
}
当然可以对它进行算法的改良,从而实现shaker排序(双向冒泡),做法只是在冒泡是采用双向移动(在移动最大值到右端后,借着执行的是选取最小值移动到最左端而非继续移动次大值)来提升效率:
void shakerSort(int *number) { /*双向冒泡排序*/
int left = 0, right = MAX - 1, flag_recond = 0;
while(left < right) {
// 向左進行冒泡排序
int i;
for(i = left; i < right; i++) {
if(number[i] > number[i+1]) {
SWAP(number[i], number[i+1]);
flag_recond = i;
}
}
right = flag_recond;
// 向右進行冒泡排序
int k;
for(k = right; k > left; k--) {
if(number[k] < number[k-1]) {
SWAP(number[k], number[k-1]);
flag_recond = k;
}
}
left = flag_recond;
}
}
数据结构 |
数组 |
---|---|
最差时间复杂度 | |
最优时间复杂度 | |
平均时间复杂度 | |
最差空间复杂度 | 总共,需要辅助空间 |
最佳算法 | No |
二. 「插入排序」
插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。基本做法是:
1 从第一个元素开始,该元素可以认为已经被排序
2 取出下一个元素,在已经排序的元素序列中从后向前扫描
3 如果该元素(已排序)大于新元素,将该元素移到下一位置
4 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5 将新元素插入到该位置后
6 重复步骤2~5
代码如下:
void insert_Sort(int *array,int first,int count){
int i,j,temp;
for(i=first+1;i<count;i++){
temp=array[i];
j=i-1;
while((j>=first)&&(array[j]>temp)){
array[j+1]=array[j];
j--;
}
array[j+1]=temp;
}
}
数据结构 | 数组 |
---|---|
最差时间复杂度 | |
最优时间复杂度 | |
平均时间复杂度 | |
最差空间复杂度 | 总共 ,需要辅助空间 |
最佳算法 | No |
三.「选择排序」
选择排序(Selection sort)是一种简单直观的排序算法。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
复杂度分析:选择排序的交换操作介于0和(n-1)次之间。选择排序的比较操作为n(n-1)/2次之间。选择排序的赋值操作介于0和3(n-1)次之间。 比较次数O(n^2),比较次数与关键字的初始状态无关,总的比较次数N=(n-1)+(n-2)+...+1=n*(n-1)/2。 交换次数O(n),最好情况是,已经有序,交换0次;最坏情况是,逆序,交换n-1次。 交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。
代码如下:
递归实现:
#define swap(x,y) {int t; t = x; x = y; y = t;}
int maxp(int *array,int n){
int flag;
if(n==1){
return 0 ;
}
flag=maxp(array,n-1);
if(array[flag]>array[n-1])
return flag;
else
return n-1;
}
void sort(int *array,int n){
int p;
if(n==1){
return ;
}
p=maxp(array,n);
swap(&array[p],&array[n-1]);
sort(array,n-1);
}
非递归实现:
void select_Sort(int *array,int count){
int i,j,max,temp;
for(i=0;i<count-1;i++){
max=i;
for(j=i+1;j<count;j++){
if(array[max]>array[j]){
max=j;
}
}
if(max!=i){
temp=array[max];
array[max]=array[i];
array[i]=temp;
}
}
}
数据结构 | 数组 |
---|---|
最差时间复杂度 | О(n²) |
最优时间复杂度 | О(n²) |
平均时间复杂度 | О(n²) |
最差空间复杂度 | О(n) total, O(1) auxiliary |
最佳算法 | 偶尔 |
四.「归并排序 」
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作,
基本做法:
1 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4 重复步骤3直到某一指针达到序列尾5 将另一序列剩下的所有元素直接复制到合并序列尾
代码如下:
void Merge_Sort(int *array, int count){
/*非递归做法--迭代算法,将序列每相邻两个数字进行归并操作,形成(n/2)个序列,
排序后每个序列包含两个元素,将上述序列再次归并,形成(n/4)个序列,
每个序列包含四个元素重复步骤2,直到所有元素排序完毕*/
int i,left_min,left_max,right_min,right_max,next;
int *tmp=(int *)malloc(sizeof(int)*count);
if(NULL==tmp){
printf("Out of memory !");
abort();
}
for(i=1;i<count;i*=2){
for(left_min=0;left_min<count-i;left_min=right_max){
left_max=right_min=left_min+i;
right_max=left_max+i;
if(right_max>count)
right_max=count;
next=0;
while (left_min < left_max && right_min < right_max)
tmp[next++] = array[left_min] > array[right_min] ? array[right_min++] : array[left_min++];
while (left_min < left_max)
array[--right_min] = array[--left_max];
while (next > 0)
array[--right_min] = tmp[--next];
}
}
}
void merge_sort(int *array,unsigned int first,unsigned int last){
int mid=0; /* 递归做法*/
if(first<last){
mid=(first&last)+((first^last)>>1); //大神写的一段代码,分离二进制中相同位,具体拿两个数作一下位运算便大概知道什么意思了。可以防止高位溢出与容错。
merge_sort(array,first,mid);
merge_sort(array,mid,last);
}
}
数据结构 | 数组 |
---|---|
最差时间复杂度 | |
最优时间复杂度 | |
平均时间复杂度 | |
最差空间复杂度 | |
最佳算法 | 有时是 |
五.「快速排序」
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
基本做法是:
1 从数列中挑出一个元素,称为 "基准"(pivot),
2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
代码如下:
void quick_Sort(int *array,int mnd,int count){
int i,j,key;
if (mnd>=count)
return ;
i=mnd;j=count;key=array[i];
while(i<j){
while(i<j&&array[j]>key)
j--;
if(i<j)
array[i++]=array[j];
while(i<j&&array[i]<key)
i++;
if(i<j)
array[j--]=array[i];
}
array[i]=key;
if(mnd<i-1)
quick_Sort(array,mnd,i-1);
if(i+1<count)
quick_Sort(array,i+1,count);
}
数据结构 | 不定 |
---|---|
最差时间复杂度 | |
最优时间复杂度 | |
平均时间复杂度 | |
最差空间复杂度 | 根据实现的方式不同而不同 |
最佳算法 | 有时是 |
六.「希尔排序 」
希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 1 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率 2 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位 .
基本做法(网络抄得):原始的算法实现在最坏的情况下需要进行O(n2)的比较和交换。V. Pratt的书[1] 对算法进行了少量修改,可以使得性能提升至O(n log2 n)。这比最好的比较算法的O(n log n)要差一些。希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。一个更好理解的希尔排序实现:将数组列在一个表中并对列排序(用插入排序)。重复这过程,不过每次用更长的列来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身仅仅对原数组进行排序(通过增加索引的步长,例如是用i += step_size而不是i++)。
代码如下:
#define SWAP( x,y) {int t; t = x; x = y; y = t;}
void shellsort( int *number) {
int i, j, k, gap, t;
gap = MAX / 2;
while( gap > 0) {
for( k = 0; k < gap; k++) {
for( i = k+gap; i < MAX; i+=gap) {
for( j = i - gap; j >= k; j-=gap) {
if( number[j] > number[j+gap]) {
SWAP( number[j], number[j+gap]);
}
else
break;
}
}
}
printf( "\ngap = %d:", gap);
for( i = 0; i < MAX; i++)
printf( "%d ", number[i]);
printf( "\n");
gap /= 2;
}
}
数据结构 | 数组 |
---|---|
最差时间复杂度 | 根据步长串行的不同而不同。 已知最好的: |
最优时间复杂度 | O(n) |
平均时间复杂度 | 根据步长串行的不同而不同。 |
最差空间复杂度 | O(n) |
最佳算法 | 非最佳算法 |
七.「.堆排序 」
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。通常堆是通过一维数组来实现的。在起始数组为 0 的情形中:
1 父节点i的左子节点在位置 (2*i+1);
2 父节点i的右子节点在位置 (2*i+2);
3 子节点i的父节点在位置 floor((i-1)/2);在堆的数据结构中,堆中的最大值总是位于根节点。
堆中定义以下几种操作:
1 最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
2 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
3 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算.
代码如下:
void sift_heap(int *array,int search_find,int len){
int i=search_find;
int tmd=2*i+1;
while(tmd<len){
if(tmd+1<len&&array[tmd]<array[tmd+1]){
tmd++;
}
if(array[i]>array[tmd]){
break;
}else{
int tmp=array[i];
array[i]=array[tmd];
array[tmd]=tmp;
}
i=tmd;
tmd=2*i+1;
}
return ;
}
void heap_sort(int *array,int count){
for(int i=count/2;i>=0;i--){
sift_heap(array,i,count);
}
for(int j=0;j<count;j++){
int t=array[0];
array[0]=array[count-j-1];
array[count-j-1]=t;
sift_heap(array,0,count-j-1);
}
}
数据结构 | 数组 |
---|---|
最差时间复杂度 | |
最优时间复杂度 | [1] |
平均时间复杂度 | |
最差空间复杂度 | total, auxiliary |
最佳算法 | 不是 |
只整理了这七种算法,还有其他好多的排序算法等以后有时间再追加吧!