排序(Sorting)就是将一组对象依照规定的次序又一次排列的过程,排序往往是为检索而服务的。它是数据处理中一种非常重要也非经常常使用的运算。比如我们日常学习中的查字典或者书籍的文件夹。这些都事先为我们排好序,因此大大减少了我们的检索时间,提高工作效率。
排序可分为两大类:
内部排序(Internal Sorting):待排序的记录所有存放在计算机内存中进行的排序过程;
外部排序(External Sorting):待排序的记录数量非常大,内存不能存储所有记录。须要对外存进行訪问的排序过程。
外部排序自己现阶段还没有接触和学习。因此这里我们仅仅研究内部排序的集中算法。希望能和大家互相学习共同进步。
内部排序的中比較常见的有四种算法,以下我们分别对各种算法常见的算法进行学习。思维导图例如以下:
基本思想:在一个已经排好序的序列中,将未被排序的元素依照原先序列的排序规则插入到序列中的指定位置。
经常使用举例:直接插入排序
直接插入排序(Straight Insertion Sorting)是一种简单的排序算法。他的基本思想是依次将每一个记录插入到一个已经排好序的有序表中去。从而得到一个新的、记录添加1的有序表。
详细步骤例如以下:
初始序列:{45 38 66 90 88 10 25 }
第一步对前两个数进行排序。把38插入到45之前,得到新的序列{45 38 66 90 88 10 25}
第二步对前三个数进行排序。把66插入到有序序列{45 38}的合适位置,即{45 38 66 90 88 10 25}
以后的步骤是在都是在以上基础上的一个递归过程知道最后一个数25插入到合适的位置得到终于序列{10 25 38 45 66 88 90}
算法描写叙述:
<span style="font-family:KaiTi_GB2312;font-size:18px;">void StraightInsertSort(List R,int n) //对顺序表R进行直接插入排序 { int i, j; for (i=2;i<=n;i++)//n为表长,从第二个记录起进行插入 { R[0] = R[i];//第i个记录复制为岗哨 j = i - 1; while (R[0].key<R[j].key)//与岗哨比較,直至键值不大于岗哨值 { R[j +1]=R[j];//将第j个记录赋值给第j+1个记录 j--; } R[j + 1] = R[0];//将第i个记录插入的序列中 } }</span>
基本思想:比較两个记录的键值大小。假设两个记录键值的大小出现逆序。则交换这两个记录,这样将键值较小的记录向序列前部移动,键值较大的记录向序列后部移动,终于将得到有序序列。
经常使用举例:冒泡排序、高速排序
冒泡排序法(Bubble Sorting)首先将第一个记录的键值和第二个记录的键值进行比較,若为逆序则将这两个记录交换,然后继续比較第二个和第三个记录的键值。以此类推,直到完毕第n-1个记录和第n个记录的键值比較交换为止。
这时便完毕了第一趟气泡,其结果是将最大的记录移到最后一位。然后第二次气泡跟第一次类似。其结果是将第二大的记录移到倒数第二位。
反复以上过程,直到整个排序过程终止得到终于有序序列。
初始序列:{45 38 66 90 88 10 25}
第一趟起泡:
第一步比較45跟38的大小。38<45所以45与38交换{38 45 66 90 88 10 25}
第二步比較45与66的大小,45<66因此不用交换位置{3845 66 90 88 10 25}
第三步比較66与90的大小。66<90不用交换{38 4566 90 88 10 25}
第四步比較90与88的大小,90>88一次交换位置{38 45 66 88 90 10 25}
第五步{38 45 66 88 10 90 25}
第六步{38 45 66 88 10 25 90}
此时第一趟起泡完毕。90为最大数。移到最后的位置,以后起泡过程跟第一趟起泡全然同样。
第一趟起泡后{38 45 66 88 10 2590}
第二趟起泡后{38 45 66 10 25 88 90}
第三趟起泡后{38 45 10 25 66 88 90}
第四趟起泡后{38 10 25 45 66 88 90}
第五趟起泡后{10 25 38 45 66 88 90}
第六趟起泡后{10 25 38 45 66 88 90}
第七趟起泡后{10 25 38 45 66 88 90}
排序完毕!
算法描写叙述:
<span style="font-family:KaiTi_GB2312;font-size:18px;"> void BubbletSort(List R,int n) { int i, j,temp,endsort; for (i=1;i<=n-1;i++) { endsort = 0; for (j = 1; j <= n - i - 1; j++) { if (R[j].key>R[j+1].key)//若为逆序则交换记录 { temp = R[j]; R[j] = R[j + 1]; R[j + 1] = temp; endsort = 1; } } if (endsort == 0) break; } }</span>
高速排序(Quick Sorting)是对冒泡排序的一种改进。它的基本思想是在n个记录中取某一个记录的键值为标准,通常取第一个记录键值为基准,通过一趟排序将待排序的记录分为小于等于这个键值和大于这个键值的两个独立的部分。这时前面部分的记录键值均比后面的记录键值小。然后对这两部分分别依照这样的方法排序。直到获得整个有序序列。
初始序列:{45 38 66 90 88 10 25}
第一步取第一个数45为标准,然后从序列末尾開始向标准数一端查找,找到第一个小于标准数的数,与标准数互换位置。
即从25開始找,25<45所以互换位置得到{25 38 66 90 88 1045}
第二步从上一步与标准数互换的数25開始向标准数一端查找,找到第一个大于标准数的数,然后与标准数互换位置。从上一步的25開始向标准数45一端即向右找到第一个大于45的数为66,然后两个数互换位置得到{25 384590 88 1066}
第三步继续从上一步与标准数互换的数66開始,向标准数45一端查找。找到第一个小于标准数的数,然后与标准数互换位置。
即从上一步66開始向45一端查找。找到第一个小于标准数45的数为10。互换位置得到{25 381090 8845 66}
第四部还是从上一步中与标准数互换的数10開始,向标准数45一端查找,找到第一个大于标准数的数为90,互换位置得到{25 38 1045 88 90 66}
第五步相同从90向45一端查找,找到第一个小于标准数45的数,然后互换位置。可是这里从90一直到45的位置都没有找到比标准数45小的数,这时便完毕了第一趟排序。比較规律可简化为左小右大。即标准数在左边就找小于标准数的数与之交换。标准数在右边就找大于标准数的数与之交换。
完毕第一趟排序之后得到序列为{25 38 1045 88 90 66},此时整个序列被分为两部分45之前的值均小于等于45,45之后的均大于45。后面的排序过程分别对这两部分序列依照上面步骤进行排序,以此类推,直到得到终于有序序列为止。
算法描写叙述:
<span style="font-family:KaiTi_GB2312;font-size:18px;"> //第一趟高速排序算法 int QuickPartition(List R,int low,int hign) { x=R[low]//赋初值。标准数 while (low < hign) { while ((low <hign) && (R[hign].key>=x.key )) hign--; R[low]=R[hign];//自尾端进行比較,将比x小的记录移到低端 while ((low <hign) && (R[low].key<=x.key )) low++; R[hign]=R[low];//自首端进行比較。将比x大的记录移到高端 } R[low]=x;//第一趟排序结束,将x移到其终于位置 return low ; }</span>
基本思想:每次在n-i+1(i=1,2,3……,n-1)个记录中选取键值最小的记录作为有序序列的第i个记录。
经常使用举例:直接选择排序、堆排序。
直接选择排序(Selection Sorting)的基本思想是在第i次选择操作中,通过n-i次键值比較,从n-i+1个记录中选出最小的记录,并和第i(1<=i<=n-1)个记录交换。
初始序列:{45 38 66 90 88 10 25}
第一步共同拥有7个数,所以n=7,首先另i=1这时候须要通过6次键值比較,从7个记录中选出最小记录,并和第1个记录交换得到{10 38 66 90 8845 25}
第二步另i=2。这时候我们须要通过六次比較。从剩余6个记录中选出最小记录,并和第2个记录交换得到{1025 66 90 88 45 38}
第三步i=3,从剩余的5个记录中找到最小记录。并和第3个记录交换得到{10 2538 90 88 45 66}
第四步i=4,从剩余的4个记录中找到最小记录。并和第4个记录交换得到{10 25 3845 88 90 66}
第五步i=5,从剩余的3个记录中找到最小记录,并和第5个记录交换得到{10 25 38 456690 88}
第六步i=6,从剩余的2个记录中找到最小记录,并和第6个记录交换得到{10 25 38 456688 90}
此时得到终于有序数列。
算法描写叙述:
<span style="font-family:KaiTi_GB2312;font-size:18px;"> void SelectSort(List R,int n) { int min, i, j; for(i=1;i<=n-1;i++)//每次循环选择出最小一个键值 { min=i;//如果第i个记录键值最小 for(j=i+1;j<=n;j++) { if(R[j].key<R[min].key) min =j;//记录下键值最小记录的下标 if(min!=i) swap(R[min],R[i]);//将最小键值记录和第i个记录交换 } } }</span>
堆排序(Heap Sorting)是利用堆的数据结构所设计的一种排序算法,可利用数组的特点高速定位指定索引的元素。堆分为最大堆和最小堆,最大堆中的任一节点的值都不小于它的两个孩子的值(若存在孩子的话)。最小堆则任一节点的值都不大于它的两个孩子的值。
以最小堆排序为例,首先要把初始序列建成一个最小堆,然后输出堆顶元素后须要重建堆。
详细步骤例如以下:
初始序列:{45 38 66 90 88 10 25}
建堆及排序如图:
算法描写叙述:
<span style="font-family:KaiTi_GB2312;font-size:18px;">void Sift(List R,int k,int m) { int i, j, x; List t; i = k; j = 2 * i; x = R[k].key; t = R[k]; while (j<=m) { if ((j < m) && (R[j].key > R[j + 1].key)) j++;//若存在右子树且右子树的keyword小,则沿右分支筛选 if (x < R[j].key) break;//筛选完成 else { R[i] = R[j]; i = j; j = 2 * i; } R[i] = t;//填入适当位置 } } //堆排序算法 void HeapSort(List R) { int i; for (i = n / 2; i >= 1; i--) Shit(R, i, n);//从第n/2个记录開始进行筛选建堆 for (i=n;i>=2;i--) { swap(R[1], R[i]);//将堆顶记录和堆中最后一个记录互换 Sift(R, 1, i - 1);//调整R[1]使R[1],……,R[i-1]变成堆 } }</span>
基本思想:将两个或两个以上的有序表结合成一个新的有序表,合并方法是比較个子序列的第一个记录的键值。最小的一个就是排序后序列的第一个记录纸。
取出这个记录。继续比較各子序列现有的第一个记录的键值,便可找出排序后的第二个记录。以此类推。终于得到有序序列。
常见举例:二路归并排序。
二路归并排序是将两个有序表结合成一个有序表,基本思想是如果序列中有n个记录,可看成是n个有序的子序列。每一个序列的长度为1.首先将每相邻的两个记录合并,得到n/2(向上取整)个较大的有序序列,每一个子序列包括2个记录。再将上述子序列两两合并,得到(n/2)/2(向上取整)个有序序列。如此重复。知道得到终于有序序列为止。
初始序列:[45] [38] [66] [90] [88] [10] [25]
一次归并后:[38 45] [66 90] [10 88] [25]
二次归并后:[38 45 66 90] [10 25 88]
三次归并后:[10 25 38 45 66 88 90]
终于得到的有序序列为:{10 25 38 45 66 88 90}
时间空间复杂度
以上我们共介绍了四类排序算法:插入、交换、选择和归并排序,重点介绍了六个内部排序算法:直接插入排序、冒泡排序、高速排序、直接选择排序、堆排序和归并排序。这几种都是我们经常使用的排序算法。我们不能单纯的评价那种算法的优劣而是要依据实际情况选择合适的算法来提高工作效率。
我仅仅是依据书中的内容加上自己的理解给大家介绍了这几种排序算法,有不足之处还请大家批评指正。