排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。
本文将介绍几种常见的内部排序
以下排序方法通常是建立在顺序表上,也就是我们通常所说的数组
时间复杂度O(n2)
算法思想:
L(0)不存放元素,作为“哨兵”(起监督作用),如下图所示,一个完整 元素数组逻辑上可看做成下面四个板块,哨兵、有序序列、L(i)、无序序列
目的是将元素L(i)插入前面板块的有序序列中,L(1)只有一个元素,故默认有序,然后将第二个元素,也就是L(2)插入到前面的有序板块中,首先将自己赋值给L(0),然后利用哨兵L(0)与前面有序板块的末位向前依次比较大小,如遇到比自己大的元素,则将该大元素向后挪一位,一直向前比较,直到遇到比自己小(算法不稳定)/小于等于(算法稳定)的元素(最差情况是遇到与自己相等的哨兵),然后插入这个元素的前面一位(直接将哨兵的值赋值给其前面一位)
举例说明:4、6、8、5、3(哨兵L(0)= L(i)= 5)
(4、6、8 处于有序序列板块,现要将5插入有序板块中;)
1.哨兵L(0)=5 与8比较,5小于8,8向后挪位,整个序列变为 4、6、8、8、3;
2.下一步:6与L(0)比较,6比L(0)大,6向后挪位,序列变为4、6、6、8、3;
3.下一步:4与L(0)比较,4比L(0)小,不挪位,将哨兵插入,第一个6换成L(0),序列变为4、5、6、8、3;
4.后面的 3 进行插入排序同理
具体程序实现如下:
//插入排序
void Insertion_sort(int A[],int n){
int i,j;
for (i = 2; i < n; ++i){//从第二个元素开始
if (A[i]<A[i-1]){//实现递增排序
A[0] = A[i];//哨兵就位
for (j = i-1; A[0]<A[j]; --j){//准备向后挪位
A[j+1] = A[j];
}
A[j+1]=A[0];//直接将哨兵的值赋值给其前面一位
}
}
}
时间复杂度O(n2)
算法思想:主体思想与上文中的直接插入排序相同,区别在于简单插入排序是在有序区逐一与哨兵比较,而折半插入排序是先定位到待插入位置LOCi,然后将有序区LOCi后面的元素向后挪位,最后将哨兵赋值安置在待插入位置Loci;
注意这里就是为了找到失败位置,即使有序区有与哨兵相同的元素,也不能像之前我们学习的折半查找一样退出(break),而是在high
//折半插入排序
void Half_insert_sort(int A[],int n){
int i,j,low,mid,high;
for (i = 2; i < n; ++i){//从第二个元素开始
A[0] = A[i];//哨兵就位
low = 1;
high = i-1;
while(low <= high){//开始折半查找插入位置(等号不能丢)
mid = (low+high)/2;
if (A[mid]<A[0]) {
low = mid + 1;
}else{
high = mid - 1;
}
}
for (j = i-1; j>high; --j){//准备向后挪位
A[j+1] = A[j];
}
A[high+1]=A[0];//将哨兵赋值安置在待插入位置Loci
}
}
空间复杂度为O(1)、时间复杂度约为O(n1.3)、最坏情况下时间复杂度为O(n2)
算法思想:隶属于插入排序,将整个排序列表以dk增量序列(此增量序列为认为随机定义,唯一要求是增量序列的最后一个元素是1,如5、3、1或者6、5、3、1或者9、5、3、1都可以)分成dk个组,然后在各个组中分别进行插入排序,注意是插入排序(简单插入排序),不是比较然后交换位置
常用增量序列(希尔推荐)是dk = n/2、dk=dk/2、、、1
具体程序实现如下:
(注意最内部的for循环中 j>0 是必须的,这个是很重要的for循环终止条件)
//希尔排序
void Shell_sort(int A[],int n){
for (int dk = n/2; dk >= 1 ; dk /=2){//步长变化
for(int i = dk+1; i<= n; i++){//在每个分组内部进行插入排序
if (A[i]<A[i-dk]){//递增排序
A[0] = A[i];//哨兵
int j;
for (j = i-dk; j>0 && A[0]<A[j]; j-=dk){//准备向后挪位
A[j+dk] = A[j];
}
A[j+dk]=A[0];
}
}
}
}
时间复杂度O(n2)
算法思想:冒泡排序比较简单,也见得比较多,也就是在原n个两两比较,然后一直将大的元素往后排(两两交换位置),直到最后,实现最大(最小)元素浮出,也就是冒泡过程,接下来在剩下的n-1个元素中同样进行此操作。
具体程序实现如下:
//冒泡排序
void Bubble_sort(int A[],int n){
int temp;
for(int i = 1;i<n;i++){
bool flag = false;//用来判断是否发生交换的标志(也可不用)
for(int j =1 ;j<n-i;j++){
if (A[j]>A[j+1]){
flag = true;
temp = A[j];
A[j] = A[j+1];
A[j+1] = temp;
}
}
if (flag == false){//判断标志位
break;
}
}
}
下文讲解快速排序应该是很清晰了,可查看下文,用python解释的,不会python的也能看明白
利用python详讲快速排序算法
时间复杂度O(n2)
简单选择是稳定的(参考于严蔚敏老师主编的《数据结构》)
算法思想:每次找到剩余元素的最小元素,然后将每趟最前面元素与该最小元素互换位置,实现最小元素沉底,最小元素一次存放于i=1、2、3…的位置
外层循环实现驱动,定位于每趟的最前面元素;内层循环致力于寻找每趟剩余元素中的最小元素的坐标;然后回到外层循环,将每趟最前面元素与该最小元素互换位置。
具体程序实现如下:
//选择排序
void Select_sort(int A[],int n){
int tempElement;
for(int i = 1;i<n;i++){
int tempkey = i;//从i开始,存放最小元素的下标
for(int j=i+1;j<n;j++){//定位最小元素的下标
if (A[j]<A[tempkey]){
tempkey = j;
}
}
tempElement = A[i];
A[i] = A[tempkey];
A[tempkey] = tempElement;
}
}
参考博文:堆排序算法(图解详细流程)
算法思想:与链表的归并排序一样的原理。
区别:在于顺序表用的是递归的方法,链表中用的是循环。
相同:声明备用空数组(链表中的是空链表),然后每次归并比较,将大的数放入备用数组,最后将备用数组中的值复制进入源数组,递归实现的时候,能实现分块存入不同的地方。
int A[] = {0,8,4,5,2,9,6,7,10,58,94,98};//A[0]是哨兵,不存放元素
display(A,12);
int B[12] = {0};
Merge_sort(A,1,11,B);//归并排序
display(A,12);
//归并排序
void Merge_sort(int A[],int low,int high,int B[]){
if(low < high){
int mid = (low+high)/2;
Merge_sort(A,low,mid,B);//分块
Merge_sort(A,mid+1,high,B);//分块
Merge(A,low,mid,high,B);//归并
}
return;
}
void Merge(int A[],int low,int mid,int high,int B[]){
int begin_first = low,begin_second = mid+1,i=low;
//归并比较,然后将大的数插入备份数组B
while(begin_first<=mid && begin_second<=high){
if (A[begin_first]<A[begin_second]){
B[i++] = A[begin_first++];
}else{
B[i++] =A[begin_second++];
}
}
//如果两段小序列中的任何一序列未排完
while(begin_first<=mid){
B[i++] = A[begin_first++];
}
while(begin_second<=high){
B[i++] = A[begin_second++];
}
//printf("B: ");①
//display(B,i);②
//将备份数组B排序元素还原到A数组
for (i = low; i <= high; ++i){
A[i] = B[i];
}
}
每次备份数组中的元素复制进入源数组时,存入的下标为(我这里下标0不存放元素):
1、2
1、2、3
1、2、3(4、5)
1、2、3、4、5
…
举例:如果我打开上面代码中的①②的注释,结果如下:
算法思想:逐层剥离(个位、十位、百位…),然后根据每次剥离的数放到不同的队列中(此处用的是一个10维数组,此处命名为桶),然后按队列(先进先出,因此稳定)读出,这串序列中最高位有多少位,就循环多少次,就可以逐个分离排序
//基数排序
void Radix_sort(int A[],int n){
int count[10];//声明一个计数数组,存储当前桶有多少个元素,便于按队列存储
int digits = getdigits(A,n),base;
int barrel[10][n];//声明一个二位桶数组
for (int i=1,multiple=1;i<=digits;++i,multiple*=10){//根据最高位有digits位,循环digits次
//count桶计数归零
for (int i = 0; i < 10; ++i){
count[i] = 0;
}
//barrel桶归零
for (int i = 0; i < 10; ++i){
for (int j = 0; j < n; ++j){
barrel[i][j] = 0;
}
}
//将每个元素分装各个桶
for (int i = 1; i < n; ++i){
int temp = A[i]/multiple;
int bottom = temp%10;
count[bottom]++;//bottom桶计数加一
barrel[bottom][count[bottom]-1] = A[i];
}
//将元素从各个桶中取出来放到A数组中
base = 1;
for (int j = 0; j < 10; ++j){
if (count[j]>0){
for(int v=0;v<count[j];v++){
A[base++] = barrel[j][v];
}
}
}
}
}
int getdigits(int A[],int n){//取得最大位数
int maxdigits = 0,tempvalue,count;
for (int i=1;i<=n;++i){
tempvalue = A[i];
count = 1;
if (tempvalue/10!=0){
count++;
tempvalue = A[i]/10;
}
while(tempvalue/10!=0){
count++;
tempvalue = tempvalue/10;
}
if (maxdigits < count){
maxdigits = count;
}
}
return maxdigits;
}