排序
排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
我们这里说说八大排序就是内部排序。
当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
1.插入排序—直接插入排序(Straight Insertion Sort)
基本思想:
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
要点:设立哨兵,作为临时存储和判断数组边界之用。
直接插入排序示例:
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <vector> #include <algorithm> using namespace std; void InsertSort(int a[],int n) { for(int i=1;i<n;i++) { if(a[i]<a[i-1]) { int x=a[i]; //哨兵 int j=i-1; while(j>=0&&x<a[j]) { a[j+1]=a[j]; j--; } a[j+1]=x; } } } int main() { int n; cin>>n; int *a=new int[n]; for(int i=0;i<n;i++) { cin>>a[i]; } InsertSort(a,n); for(int j=0;j<n;j++) { cout<<a[j]; } delete []a; }
2. 插入排序—希尔排序(Shell`s Sort)
希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序
基本思想:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
操作方法:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
希尔排序的示例:
算法实现:
我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 .....1} n为要排序数的个数
即:先将要排序的一组记录按某个增量d(n/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <vector> #include <algorithm> using namespace std; void ShellSort(int a[],int n) { int dk; int temp; int j; for(dk=n/2;dk>0;dk/=2) { for(int i=dk;i<n;i++) { temp=a[i]; //对每一个增量段直接插入排序 for(j=i;j>=dk;j=j-dk) { if(temp<a[j-dk]) a[j]=a[j-dk]; else break; } a[j]=temp; } } } int main() { int n; cin>>n; int *a=new int[n]; for(int i=0;i<n;i++) { cin>>a[i]; } ShellSort(a,n); for(int j=0;j<n;j++) { cout<<a[j]; } delete []a; }
3. 选择排序—简单选择排序(Simple Selection Sort)
基本思想:
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
简单选择排序的示例:
操作方法:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推.....
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,
直到整个序列按关键码有序。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <vector> #include <algorithm> using namespace std; //简单选择排序:遍历一次找到最小与第一个元素呼唤位置,再从第二个元素开始遍历找到最小与第二个元素呼唤位置... void SelectSort(int a[],int n) { int temp; for(int i=0;i<n-1;i++) { for(int j=i+1;j<n;j++) { if(a[j]<a[i]) { temp=a[i]; a[i]=a[j]; a[j]=temp; } } } } int main() { int n; cin>>n; int *a=new int[n]; for(int i=0;i<n;i++) { cin>>a[i]; } SelectSort(a,n); for(int j=0;j<n;j++) { cout<<a[j]; } delete []a; }
4. 选择排序—堆排序(Heap Sort)
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
基本思想:
堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足
时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:
(a)大顶堆序列:(96, 83,27,38,11,09)
(b) 小顶堆序列:(12,36,24,85,47,30,53,91)
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
因此,实现堆排序需解决两个问题:
1. 如何将n 个待排序的数建成堆;
2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。
首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:
1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
2)将根结点与左、右子树中较小元素的进行交换。
3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).
4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).
5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
称这个自根结点到叶子结点的调整过程为筛选。如图:
再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。
1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。
2)筛选从第个结点为根的子树开始,该子树成为堆。
3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
算法的实现:
从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <vector> #include <algorithm> using namespace std; //第二步调整小根堆,从小到大 void HeapAdjust(int a[],int s,int n) { int rc =a[s]; //顶点 for(int j=2*s;j<=n;j=j*2) { if(j<n&&a[j]>a[j+1]) //取a[j]和a[j+1]中较小的值的下标,记为j j++; if(rc<=a[j]) break; //rc小于左右子树中最小的,则rc已经是最小的,当前节点s已调整为最小堆 else { a[s]=a[j]; //用最小的节点a[j]替换a[s] s=j; //改动了a[j],所以要将j节点重新调整为小根堆 } } a[s]=rc; } //第一步:建初堆 void CreatHeap(int a[],int n) { //小根堆 for(int i=n/2;i>0;i--) { HeapAdjust(a,i,n); } } //整合 void HeapSort(int a[],int n) { CreatHeap(a,n); for(int i=n;i>1;i--) { int x=a[1]; a[1]=a[i]; a[i]=x; HeapAdjust(a,1,i-1); } } int main() { int n; cin>>n; int *a=new int[n+1]; for(int i=1;i<=n;i++) { cin>>a[i]; } HeapSort(a,n); for(int j=1;j<=n;j++) { cout<<a[j]; } delete []a; } //输入: //8 //49 38 65 97 76 13 27 49 //输出: //9776654949382713
5. 交换排序—冒泡排序(Bubble Sort)
基本思想:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
冒泡排序的示例:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <vector> #include <algorithm> using namespace std; void maopao(int a[],int n) { for(int i=0;i<n-1;i++) { for(int j=0;j<n-i-1;j++) { if(a[j]>a[j+1]) { int t=a[j]; a[j]=a[j+1]; a[j+1]=t; } } } } int main() { int n; cin>>n; int *a=new int[n+1]; for(int i=0;i<n;i++) { cin>>a[i]; } maopao(a,n); for(int j=0;j<n;j++) { cout<<a[j]; } delete []a; } /* 输入: 8 49 38 65 97 76 13 27 49 输出: 9776654949382713 */
6. 交换排序—快速排序(Quick Sort)
基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
快速排序的示例:
(a)一趟排序的过程:
(b)排序的全过程
算法的实现:
递归实现:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <vector> #include <algorithm> using namespace std; void kuaipai(int array[],int low,int hight) { int i,j,t,m; if(low<hight) { i=low; j=hight; t=array[low]; //第一个数为轴 while(i<j) { while(i<j&&array[j]>t) //从右边找出小于轴的数 { j--; } if(i<j) //将小于轴的数array[j]放到左边array[i]的位置 { m=array[i]; array[i]=array[j]; array[j]=m; i++; } while(i<j&&array[i]<=t) //从左边找出大于轴的数 i++; if(i<j) { m=array[j]; array[j]=array[i]; array[i]=m; j--; } } array[i]=t; //轴放在中间,现在就有两个区域分别是[0,i-1]和[i+1,hight],分别快排 kuaipai(array,0,i-1); kuaipai (array,i+1,hight); } } void px_kuaipai(int buf[],int sizes) { kuaipai(buf,0,sizes-1); } int main() { int n; cin>>n; int *a=new int[n]; for(int i=0;i<n;i++) { cin>>a[i]; } px_kuaipai(a,n); for(int j=0;j<n;j++) { cout<<a[j]; } delete []a; } /* 输入: 8 49 38 65 97 76 13 27 49 输出: 9776654949382713 */
7. 归并排序(Merge Sort)
基本思想:
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
归并排序示例:
合并方法:
设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m。
- j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标
- 若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束
- //选取r[i]和r[j]较小的存入辅助数组rf
如果r[i]<r[j],rf[k]=r[i]; i++; k++; 转⑵
否则,rf[k]=r[j]; j++; k++; 转⑵ - //将尚未处理完的子表中元素存入rf
如果i<=m,将r[i…m]存入rf[k…n] //前一子表非空
如果j<=n , 将r[j…n] 存入rf[k…n] //后一子表非空 - 合并结束。