目录
一、详解8种排序算法:
插入排序:1.直接插入排序 2.希尔排序
交换排序:3.冒泡排序 4.快速排序
选择排序:5.简单选择排序 6.堆排序
归并排序:7.归并排序:
桶排序 : 8.桶排序
详解:
1.直接插入排序:
将一个记录插入到已排序好的有序表中,从而得到一个新且记录数加1的有序表。
即:先将序列的第一个记录看作是一个有序的子序列,然后从第二个记录逐个进行插入,直到整个序列有序为止。
例如:给定一个序列{49,38,65,97,76,13,27,49},利用插入排序的过程部分如图所示:
图中给出前六步,无论什么时候序列中都是两部分已排序部分和未排序部分。每次取未排序部分的第一个元素,与已排序部分的元素进比较之后插入到合适的位置,完成排序。
那么已排序部分加1,未排序部分减1.依次类推,直到整个排序完成。
具体算法实现如下:
void InsertSort(int arr[],int len)
{
int tmp = 0;//临时量记录有序序列的值
int i = 0;//控制无序序列的走向
int j = i+1;//控制有序序列的走向
for(i;i<len;i++)
{
tmp = arr[i];
for(j = i-1;j >= 0 && arr[j]> tmp;j--)
{
arr[j+1] = arr[j];//
}
arr[j+1] = tmp;
}
}
2.希尔排序:
先将整个待排序列的记录序列分割为若干个子序列,分别进行直接插入排序,待整个序列中的记录”基本有序“时,再对全体记录进行依次的直接插入排序。
例如:给定一个序列{49,38,65,97,76,13,27,49,55,04},利用希尔排序的过程如图所示:
每一趟都将序列划分为不同的组,进行组内排序,“基本有序”后,对全体进行直接插入排序得到最终的结果。
具体算法实现如下:
void Shell(int arr[], int len, int dk)//dk是增量区间
{
int tmp = 0;
int i = dk;
int j = i - dk;
for (i; i < len; ++i)
{
tmp = arr[i];
for (j = i - dk; j >= 0 && arr[j] > tmp; j -= dk)
{
arr[j + dk] = arr[j];
}
arr[j + dk] = tmp;
}
}
void ShellSort(int arr[], int len, int dk[], int dlen)
{
for (int i = 0; i < dlen; ++i)
{
Shell(arr, len, dk[i]);
}
}
3.冒泡排序:
冒泡排序的思想很简单。首先将第一个关键字和第二个记录的关键字进行比较,若为前面的数大于后边的数,则将两个记录交换之,然后比较第二个记录和第三个记录的关键字,直到第n-1个记录和第n个记录的关键字进行过比较为止。
例如:给定一个序列{49,38,65,97,76,13,27,49},利用冒泡排序的过程如下图所示:
注意趟数的控制和比较的次数的控制。
具体算法如下:
void BulleSort(int arr[],int len)
{
int flag = false;//消除重复的比较
for(int i = 0;i<len-1;i++)//控制比较趟数
{
for(int j=0;j<len-i-1;j++)//控制比较次数
{
flag = true;
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
if(!flag)
{
return;
}
}
}
4.快速排序
通过一趟排序将待排部分分割为独立的两部分,其中一部分记录的关键字均另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
首先任意选取一个记录作为枢轴,通常选取第一个记录,然后设置两个指针low和high,一个从头向后遍历另一个从尾部向前遍历,遍历的过程中,将所有小于选取的枢轴的记录放在其左部分,另外大于枢轴的部分放在其右部分,直到两个指针相遇,将枢轴记录放到相遇点,作为分界线;依次类推,直到有序;
例如:给定一个序列{49,38,65,97,76,13,27,49},利用快速排序的过程如下图所示:
快排的思想主要是划分,利用前后指针遍历序列。
int Patition(int arr[],int left,int right)
{
int key = arr[left];
while(left < right)
{
while(left < right && arr[right] > key)
right--;
arr[left] = arr[right];
while(left < right && arr[left] < key)
left++;
arr[right] = arr[left];
}
arr[right] = key;
return left;
}
void Quick(int arr[],int startindex,int endindex)
{
if (startindex < endindex)
{
int k = Patition(arr, startindex, endindex);
Quick(arr, startindex, k - 1);
Quick(arr, k + 1, endindex);
}
}
void QuickSort(int arr[], int len)
{
Quick(arr, 0, len - 1);
}
5.简单选择排序
通过n-i次关键字间的比较,从n-i+1个记录中选择关键字最小的记录,并和第i(1<=i<=n)个记录交换之。
具体的算法为:令i从0到n-1,进行n-1趟简单选择操作;令j为i+1到n,是从i之后的待排序序列中选取最小的且小于i位置下的关键字的记录关键字,与i位置下的关键字进行交换;
例如:给定一个序列{49,89,34,18,51},利用简单选择排序的过程如图所示;
具体算法实现如下:
void SimpleSelectSort(int arr[],int len)
{
int min = 0;
for(int i = 0;i < len-1;i++)
{
min = i;
for(int j = i+1;j < len;j++)
{
if(arr[j] < arr[min])
{
min = j;
}
}
if(min != i)
{
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
}
6.堆排序:
第一步:根据所给的记录序列,构建出一棵完全二叉树;
第二步:对该完全二叉树进行调整,将其调整为大堆或者小堆;
这里要明确大堆和小堆的概念:
大堆:指的是根节点大于他左右孩子
小堆:指的是根节点小于他左右孩子
第三步:将最后一个元素和对顶元素进行交换,得到最大或最小的元素值;该最大或最小的元素值位于堆的最后一个叶子节点处;
第四步:调整除最后一个元素外的堆,使其成为初始定义的大堆或者小堆;
第五步:重复三、四不,直到整个序列有序为止;
例如:给定一个序列{49,89,34,18,51},利用堆排序的过程如图所示;
算法实现如下:
/*
所以在这里,分为两个功能,一是堆的调整,二是堆的排序
*/
void HeapAdjust(int arr[],int pos,int len)
{
//根节点:i
//左结点:2*i+1
//右结点:2*i+2
int i = pos;
int j = 2 * i + 1;
int tmp = 0;
for (j; j < len; j = 2 * i + 1)
{
if(j < len - 1 && arr[j+1] > arr[j])
++j;
if(arr[i] >= arr[j])
break;
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i = j;
}
}
void HeapSort(int arr[],int len)
{
//交换
int tmp = 0;
for(int i = len/2-1;i>=0;--i)
{
HeapAdjust(arr,i,len);//调整i节点的堆关系
}
for(int i = len -1;i > 0;--i)
{
tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
HeapAdjust(arr, 0, i);//调整根节点的堆关系
}
}
7.归并排序
“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。利用归并的思想容易实现排序。
假设初始化序列含有n个记录,则可堪称是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到2/n个长度为2或1的有序子序列;再两两归并,...如此重复,直到得到一个长度为n的有序序列为止;
归并排序是基于分治法的,
第一步:递归划分初始序列,直到每个元素为一个序列;
第二步:划分完成后,再进行回溯,对每个序列进行排序合并;
例如:给定一个序列{49,89,34,18,51},利用归并排序的过程如图所示;
具体算法实现如下:
void Merge(int arr[], int tmp[], int startindex,int mid, int endindex)
{
int i = startindex;
int j = mid + 1;
int k = startindex;
while (i < mid + 1 && j < endindex + 1)
{
if (arr[i] <= arr[j])
{
tmp[k++] = arr[i++];
}
if (arr[i] > arr[j])
{
tmp[k++] = arr[j++];
}
}
while (i < mid + 1)
{
tmp[k++] = arr[i++];
}
while (j < endindex + 1)
{
tmp[k++] = arr[j++];
}
for (int i = startindex; i <= endindex; ++i)
{
arr[i] = tmp[i];
}
}
void MergeS(int arr[], int tmp[], int startindex, int endindex)
{
if (startindex < endindex)
{
int mid = (startindex + endindex) / 2;
MergeS(arr, tmp, startindex, mid);
MergeS(arr, tmp, mid + 1, endindex);
Merge(arr, tmp, startindex, mid, endindex);
}
}
void MergeSort(int arr[], int len)
{
int *tmp = (int*)malloc(sizeof(arr[0])*len);
MergeS(arr, tmp, 0, len - 1);
free(tmp);
}
8.桶排序又称基数排序
这个排序是和前面所学的排序方法完全不同的一种排序。这个排序方法是一种借助多关键字排序的思想对单逻辑关键字子进行排序的方法;
桶排序的基本步骤是:
第一步:遍历整个序列,找到最大的关键字,并获取该关键字的位数;
第二步:建立”桶“这一结构,利用二维数组实现;需要表示该“桶”的编号,以及每个”桶“内的编号;
第三步:遍历数组,从第一个关键字开始,获取其个位数字,根据所获得的数字将该关键字放到与所获数字对应的“桶”编号内;
第四步:一次结束后,将“桶”内的关键字一次出”桶“;
第五步:对出桶的元素重复三四步,直到获得到最高位的元素并出桶完成;
注意:
桶排序在大多数情况下是要快于快速排序的;
桶排序是利用空间替换时间,需要的空间大,但时间复杂度低;
例如:给定一个序列{154,8956,3210,2015,132,452},利用归并排序的过程如图所示;
具体算法实现如下:
int FindMaxFinger(int arr[], int len)
{
int maxnum = arr[0];
for (int i = 1; i < len; ++i)
{
if (arr[i]>maxnum)
{
maxnum = arr[i];
}
}
int count = 0;
while (maxnum != 0)
{
maxnum /= 10;
count++;
}
return count;
}
int FindFinNumber(int num, int fin)
{
return num / (int)pow(10.0, fin) % 10;
}
void Radix(int arr[], int len, int fin)
{
int backet[10][MAXSIZE] = {};
int finnum = 0;
int num[10] = {};
for (int i = 0; i < len; ++i)
{
finnum = FindFinNumber(arr[i], fin);
backet[finnum][num[finnum]] = arr[i];
num[finnum]++;
}
int aindex = 0;
int bindex = 0;
for (int i = 0; i < 10; ++i)
{
bindex = 0;
while (bindex != num[i])
{
arr[aindex++] = backet[i][bindex++];
}
}
}
void RadixSort(int arr[], int len)
{
int max = FindMaxFinger(arr, len);
for (int i = 0; i < max; ++i)
{
Radix(arr, len, i);
}
}
二、总结
对于以上的8种内排序方法,对其进行时间复杂度和空间复杂度分析,如以下表格:
排序名称 | 时间复杂度 | 空间复杂度 | 稳定性 | 适用条件 |
直接插入 | 最好:O(n) 最坏:O( 平均:O( | O(1) | 稳定 | 适用于数据基本有序的序列 |
希尔排序 | 最好:O(n) 最坏:O( 平均:O( | O(1) | 不稳定 | 适用于数组较长且初态无序 |
冒泡排序 | 最好:O(n) 最坏:O( 平均:O( | O(1) | 稳定 | 适用于数据基本有序的序列 |
快速排序 | 最好:O(nlogn) 最坏:O( 平均:O(nlogn) | 最好:O(logn) 最坏:O(n) | 不稳定 | 适用于数组是随机排序的情况,当数组基本有序时,快排的作用还不如插入排序。 |
简单选择排序 | 最好:O( 最坏:O( 平均:O( | O(1) | 不稳定 | 适用于数据基本有序的序列 |
堆排序 | 最好:O(nlogn) 最坏:O(nlogn) 平均:O(nlogn) | O(1) | 不稳定 | 适用于记录数较大的情况。 |
归并排序 | 最好:O(nlogn) 最坏:O(nlogn) 平均:O(nlogn) | O(n) | 稳定 | 基本都是适用。 |
桶排序 | 最好:O(d(r+n)) 最坏:O(d(r+n)) 平均:O(d(r+n)) 平均:O(d(r+n)) | O(rd+n) | 稳定 | 适用于n值很大并且关键字较小的序列。 |
对于这8种排序方法,没有哪一种是绝对最优的,对于不同的n值,有各自最适应的排序算法。有时候还可以多个综合使用。