废话不说上干货,众所周知,排序算法有七种,三简四复杂。分别是冒泡排序、直接插入排序、选择排序;希尔排序、堆排序、归并排序和快速排序。首先将七种排序的对比图给出:
几种算法比较的具体解释等讲完所有算法之后再论。现在上详解!
冒泡排序
冒泡排序是我接触到的第一种算法,当然彼时我还不知道这小小的排序也能称得上算法。所谓冒泡法,就是像小鱼吐泡泡一样,两两相邻对比,小鱼吐泡泡肯定是一个一个吐嘛。
算法原理描述(默认从小到大排序):
每一个元素都和它的后(前)一个元素比较,若前者大于后者,则两者交换,否则继续后移(前移)。为了减少比较次数与移动次数,我们采取以下两种方法:第一,从后向前进行判断;第二定义以个标志位,若后面的元素已经排好序则不用再进行重复的比较与判断。下面直接上代码:
void BubbleSort(int k[],int n)
{
int i,j,tmp;
bool flag = true;
for(i=0;i<n && flag;i++)
{
for(int j=n-1;j<i;j--)
{
flag = false;
if(k[j]<k[j-1])
{
tmp = k[j];
k[j] = k[j-1];
k[j-1] = tmp;
flag = true;
}
}
}
}
如果有人有兴趣的话可以去掉flag为比较一下这个标志位存在的重要性!
选择排序
冒泡排序中主要是通过比较和交换完成最终排序的,但交换也是影响实际速率的一个因素,因此下面来讲解一种较少交换次数的新的排序算法:选择排序。
算法描述:
数组k共有元素,通过n-i次的比较,从n-(i-1)中选取最小的那个元素和第i个元素交换,重复上述操作,直到完成排序为止。
实例:
输入数组: 9、2、1、7、5
第一次:将9分别与其他数组依次比较,将最小数的索引放在一个中间变量中。比较完之后发现中间变量中存放的是2,也就是说,第一个数要与第三个数互换,现在第一个元素就是最小数字。现在数组是1、2、9、7、5
然后做第二次比较,首先将2与其它三个数字依次对比,发现中间变量中存放的就是2的索引1,所以不交换,直接进行下一次比较。现在数组是1、2、9、7、5。
现在第三个数组元素是9,依次与其他两个数比较,中间变量存放的是4也就是说第三个数与第五个书互换,结果是:1、2、5、7、9
最后比较7和9 发现中间存放的变量等于7的索引,所以最终数组已完成排序。
现在上代码:
void SelectSort(int k[],int n)
{
int i,j,min,tmp;
for(i=0;i<n;i++)
{
min = i;
for(j=i+1;j<n;j++)
{
if(k[j]<k[min])
{
min=j;
}
}
if(min!=i)
{
tmp = k[min];
k[min]=k[i];
k[i]=tmp;
}
}
}
直接插入排序
直接插入排序是最简单的排序方法,但是概念稍微有一点绕。直接插入排序是将待排序数列中无序的部分直接插入到有序部分中的合适位置。大家可能并不理解这句话的意思,没关系不重要,咱们在例子中学习方法。
假设输入的数组是5、7、 0、 3
首先比较第一个和第二个元素,因为5和7现在属于有序排序,即5确实小于7 那这两个不做操作;
下面比较第二个元素和第三个元素,因为7>0 所以要将0插入到5和7中合适的位置。首先将0放入到中间变量tmp中,因为7>tmp所以将7后移一位,再比较tmp和5,因为5>tmp,所以5后移1位,而将tmp放入到5的位置。现在的数组是 0、5、7、 3;
现在 0、5、7是有序序列,将3插入其中。首先令tmp=3,比较7>tmp,所以7后移一位,同样的5也后移一位,再比较0与tmp因为0
void InsertSort(int k[],int n)
{
int i,j,tmp;
for(i=1;i<n;i++)
{
if(k[i]<k[i-1])
{
tmp = k[i];
for(j=i-1;j>=0&&k[j]>tmp;j--)
{
k[j+1]=k[j];
}
k[j+1]=tmp;
}
}
}
希尔排序——来源于直接插入算法
上边提到的三种算法都是时间复杂度为O(n^2)的排序算法,而人们不满足于此,所以提出了新的排序算法:希尔排序算法。
希尔排序算法是将已给定的数组分成若干个分块进行直接插入比较,最后将几个分块比较得到最终的有序序列。
上实例:
输入数组 k:45 、36 、63 、98 、76 、13 、27 、49 、55 、4
数组大小为10,首先比较与本元素相隔为gap=10/2=5(这个值有很多种选取方法,个人喜好)的元素比较,即k[0]与k[5] 、k[1]与k[6]、k[2]与k[7] 、k[3]与k[8] 、k[4]与k[9] 若前者大于后者,则两者互换,最终得到的新序列如下:
13 、27 、49 、55 、4、45 、36 、63 、98 、76
第二次进行直接插入排序时,gap=5/2 = 2
所以k[0]、k[2]、k[4]和k[6]、k[8]比较
k[1]、k[3]、k[5]、k[7]、k[9]比较
得到的最新序列如下:
4、 27、 13、 45、 36、 55、 49、 63、 98 、76
第三次 gap=2/2 =1
比较所有的元素,最终得到以下序列:
4、 13、 27、 36、 45、 49、 55、 63、 76、 98
其中所有的子序列均使用直接插入排序法。
代码如下:
void ShellSort(int k[],int n)
{
int i,j,tmp,gap=n/2;
while(gap>0)
{
for(i=gap;i<n;i++)
{
if(k[i-gap]>k[i])
{
tmp =k[i];
for(j=i-gap;j>=0&&k[j]<tmp;j-=gap)
{
k[j+gap]=k[j];
}
k[j+gap]=tmp;
}
}
gap /=2;
}
}
堆排序——来源于选择排序
利用前一次排序的结果,是对选择排序进行改进。
堆跟完全二叉树的定义类似。
完全二叉树的定义:假设二叉树共有n层,除根节点之外的其它层的结点都达到了二叉树的最大个数
大顶堆定义:大顶堆是具有以下性质的的完全二叉树,每个根节点的值都大于或等于子节点的值。
小顶堆:每个根节点的值都小于等于子节点的值的完全二叉树。
根节点是堆中所有节点最大或者最小值,如果按照层序遍历的方法给结点从1开始编号,则结点之间满足如下关系:
堆排序算法就是利用堆进行排序的算法.
它的基本思想是:将待排序的序列构成一个大顶堆(或小顶堆);此时整个序列的最大值就是堆顶的根节点。将它移走(就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值);然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便能得到一个有序序列了。
下边直接上代码:
void swap(int k[],int i, int j)
{
int tmp = k[i];
k[i] = k[j];
k[j] = tmp;
}
void HeapAdjust(int k[],int s,int n) //s是起始节点,n为长度
{
int i , tmp;
tmp = k[s];
for( i =2*s;i<=n;i*=2 ) //并不仅仅是为了寻找最大值,而是将元素放到该待的位置。
{
if( i<n &&k[i] <k[i+1])
{
i++;
}
if(tmp >=k[i])
{
break;
}
k[s] = k[i];
s=i;
}
k[s] = tmp;
}
void HeapSort( int k[], int n)
{
int i;
for( i = n/2;i>0;i--)
{
HeapAdjust(k,i,n);
}
for(i=n;i>1;i--)
{
swap(k,1,i);
HeapAdjust(k,1,i-1);
}
}
归并排序(Merge Sort)
将两个或者两个以上的有序序列合并成一个有序序列的操作叫归并。
归并排序就是利用归并的思想实现的排序方法。原理:假设初始序列有n个记录,则可以看成是n个有序的字序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为2或者1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
递归实现:
//实现归并,并将所有的数据放到list1中
void merging(int *list1,int *list2,int list1_size, int list2_size)
{
int i,j,k;
int tmp[MAXSIZE];
i=j=k=0;
while(i<list1_size && j <list2_zise)
{
if(k[i]<k[j])
{
tmp[k++] = list1[i++];
}
else
{
tmp[k++] = list2[j++];
}
}
while(i<list1_size)
{
tmp[k++] = list1[i++];
}
while ( j<list2_size)
{
tmp[k++] = list2[j++];
}
for(int m = 0;m<list1_size+list2_size;m++)
{
list1[m] = tmp[m];
}
}
void MergeSort(int k[], int n)
{
if(n>1)
{
int *list1 = k;
int list1_size = n/2;
int *list2 = k+list1_size;
int list2_size = n-list1_size;
MergeSort(list1,list1_size);
MergeSort(list2,list2_size);
merging(list1,list2, list1_size,list2_size);
}
}
递归需要不断的调用自己,对空间和时间会有很大的消耗,因此下面用迭代方法实现归并排序。
void MergeSort( int k[], int n)
{
int i,left_min, left_max,right_min,right_max,next;
int *tmp ;
for( i=1;i<n;i*=2) //步长
{
for(left_min =0; left_min <n-i;left_min =right_max)
{
right_min =left_max= left_min +i;
right_max = right_min +i;
if (right_max> n)
rifht_max = n;
next = 0;
while(left_min <left_max && right_min <right_max)
{
if( k[left_min]<k[right_min])
{
tmp[next++] = k[left_min++];
}
else
{
tmp[next++] = k[right_min++];
}
}
while(left_min<left_max)
{
k[--right_min] =k[--left_max];
}
while (next >0)
{
k[--right_min] = tmp[--next];
}
}
}
}
快速排序——二十世纪十大算法之一
快速排序算法属于交换排序类。
原理:每趟排序指定一个元素作为基准点,把小于基准点的元素放于左侧,大于基准点的放于右侧。关键点:选择基准点!!!
void swap(int k[],int i,int j)
{
int tmp = k[i];
k[i] = k[j];
k[j] = tmp;
}
int Partition(int k[], int low, int high)
{
int point ;
point = k[low]; //基准点
while(low <high )
{
while( low< high && k[high] >=point) //比较右边
{
high--;
}
swap(k,low,high);
while( low< high && k[low] <=point) //比较左边
{
low++;
}
swap(k,low,high);
}
return low;
}
void QSort(int k[] , int low, int high)
{
int point;
if(low<high)
{
point =Partition(k,low,high);
QSort(k,low,point-1);
QSort(k,point+1,high);
}
}
void QuickSort( int k[] , int n)
{
QSort(k,0,n-1);
}
但是上述代码中对基准点的选择过于草率,所以提出对上述算法进行优化。
第一种优化:优化选取基准点
选择第一个元素、最后一个元素和中间元素中排序位于中间的点作为基准点。
修改Partition函数
int Partition(int k[], int low, int high)
{
int point ;
int m = low+(high-low)/2;
if(k[low]>k[high])
swap(k,low,high);
if(k[m]>k[high])
swap(k,m,high);
if(k[m]<k[low])
swap(k,m,low);
point = k[m]; //基准点
while(low <high )
{
while( low< high && k[high] >=point) //比较右边
{
high--;
}
swap(k,low,high);
while( low< high && k[low] <=point) //比较左边
{
low++;
}
swap(k,low,high);
}
return low;
}
第二种优化方法:优化掉不必要的交换
将交换改成赋值
int Partition(int k[], int low, int high)
{
int point ;
int m = low+(high-low)/2;
if(k[low]>k[high])
swap(k,low,high);
if(k[m]>k[high])
swap(k,m,high);
if(k[m]<k[low])
swap(k,m,low);
point = k[m]; //基准点
while(low <high )
{
while( low< high && k[high] >=point) //比较右边
{
high--;
}
k[low] = k[high];
while( low< high && k[low] <=point) //比较左边
{
low++;
}
k[high] = k[low];
}
k[low] = point;
return low;
}
第三种优化
当数量较小时,用直接插入法更快,快速排序适合大型数组,不适合小数量的数组。当数组元素小于7时用直接插入排序。
优化QSort函数
void QSort(int k[] , int low, int high)
{
int point;
if(high-low>7)
{
point =Partition(k,low,high);
QSort(k,low,point-1);
QSort(k,point+1,high);
}
else
InsertSort(k,low,high);
}
void InsertSort(int k[], int low, int high)
{
ISort(k+low,high-low);
}
void ISort(int k[].int n)
{
int I,j,tmp;
for(i=1;i<n;i++)
{
if(k[i]<k[i-1])
{
tmp = k[i];
for(j=i-1;k[j]>tmp;j--)
{
k[j+1]=k[j];
}
k[j+1] = tmp;
}
}
}
第四种优化方式:优化递归方式:
将递归放在函数的末尾,即写成尾递归的形式。
大大缩减栈空间,提高运行效率。
修改QSort函数:
void QSort(int k[] , int low, int high)
{
int point;
if(high-low>7)
{
while(low<high)
{
point =Partition(k,low,high);
if(point-low <high – point)
{
QSort(k,low,point-1);
low =point+1;
}
else
{
QSort(k,point+1,high);
high = point -1;
}
}
else
InsertSort(k,low,high);
}