写在文章前头的东西
- 排序函数接口统一:void XSort(ElementType A[], int N);
- 简单起见,程序均为从小到大排序;
- 只讨论基于比较的排序;
- 稳定性:任意相等的两个元素,在排序过程中的相对位置不会发生改变;
- 没有任何一种算法在任何情况下就表现最好;
- 笔者自从发现思维导图有奇效后,哪哪儿都喜欢画画思维导图,因此本文会出现比较多的思维导图,不适应者可避开雷区。
٩꒰▽ ꒱۶⁼³₌₃
冒泡排序
- 实现代码:
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
void BubbleSort(ElementType A[], int N)//统一函数接口
{
int P;
for (P = N - 1; P >= 0; P--){
int flag = 0;
for (int i = 0; i < P; i++){
if (A[i] > A[i+1]){
int temp = A[i];
A[i] = A[i+1];
A[i+1] = temp;
flag = 1;
}
}//一轮交换完成,进行下一轮交换
if (flag == 0)
break;//该轮并未发生任何交换,排序结束
}
}
int main(int argc, char const *argv[])
{
ElementType a[] = {21, 24, 56, 87, 90, 76};
BubbleSort(a, 6);
for (int i = 0; i < 6; i++){
if (i == 5)
printf("%d\n",a[i]);
else
printf("%d ",a[i]);
}
system("pause");
return 0;
}
插入排序
- 这里要介绍一下逆序对的概念:
- 对于下标i < j, 如果A[i]>A[j],则称(i, j)是一对逆序对;
- 交换两个相邻元素正好消去一个逆序对
- 如果序列基本有序,则插入排序简单且高效。
- 实现代码:
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
void InsertionSort(ElementType A[], int N)//统一函数接口
{
int P;
for (int i = 1; i < N; i++){//假设第一张牌已经在手上
int temp = A[i];//摸到的牌
for (P = i; P > 0 && temp < A[P - 1]; P--)
//依次与前面的牌进行比较
A[P] = A[P - 1];//将前面的牌往后挪
A[P] = temp;//插入
}
}
int main()
{
int a[] = {21, 24, 56, 87, 90, 76};
InsertionSort(a, 6);//从小到大排序
for (int i = 0; i < 6; i++){
if (i == 5)
printf("%d\n",a[i]);
else
printf("%d ",a[i]);
}
system("pause");
return 0;
}
希尔排序
- 前面我们提到了逆序对的概念,下面介绍两个定理:
- 综上所述,要提高算法效率,我们必须
- 每次消去不止1个逆序对;
- 每次交换相隔较远的2个元素。
- 实现代码:(
分别用原始Shell增量序列与Sedgewick增量序列)
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
//希尔排序核心就是插入排序
void ShellSort_1(ElementType A[], int N)//统一函数接口
//原始ShellSort
{
int i, temp, j;
for (int P = N/2; P > 0; P /= 2){
for (int i = P; i < N; i++){//插入排序
temp = A[i];
for (j = i; j >= P && temp < A[j - P]; j -= P)
A[j] = A[j - P];
A[j] = temp;
}
}
}
void ShellSort_2(ElementType A[], int N)//统一函数接口
//用Sedgewick增量排序
{
//这里只列出一小部分增量
int Si, j, temp;
int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};
for ( Si = 0;Sedgewick[Si] >= N; Si++ ){}//初始的增量Sedgwick[Si]不能超过待排序列的长度
for (int P = Sedgewick[Si]; P > 0; P = Sedgewick[++Si]){
for (int i = P; i < N; i++){//插入排序
temp = A[i];
for (j = i; j >= P && temp < A[j - P]; j -= P)
A[j] = A[j - P];
A[j] = temp;
}
}
}
int main()
{
int a[] = {21, 24, 56, 87, 90, 76};
ShellSort_1(a, 6);//从小到大排序
ShellSort_2(a, 6);//从小到大排序
for (int i = 0; i < 6; i++){
if (i == 5)
printf("%d\n",a[i]);
else
printf("%d ",a[i]);
}
system("pause");
return 0;
}
堆排序
- 顾名思义,堆排序是使用堆这个结构的排序算法(什么是堆)
- 定理:堆排序处理N个不同元素的随机排序的平均比较次数是。
- 虽然堆排序给出最佳的平均时间复杂度,但是其实际效果不如用Sedgewick增量序列的希尔排序。
- 实现代码(
与堆的操作集非常类似,但也有不同点,具体不同点见上图与代码)
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
void Swap(ElementType *a, ElementType *b);
void HeapSort(ElementType A[], int N);//统一函数接口
void PercDown(ElementType A[], int p, int N);
void Swap(ElementType *a, ElementType *b)
{
ElementType temp = *a;
*a = *b;
*b = temp;
}
void HeapSort(ElementType A[], int N)//统一函数接口
{
int i;
for (i = (N-1)/2; i >=0; i--)
PercDown(A, i, N);//将A[]调整为最大堆
for (i = N-1; i >= 0; i-- ){
Swap(&A[0], &A[i]);//交换最大堆顶与堆最后一个元素
PercDown(A, 0, i);//重新调整为最大堆且堆的大小-1
}
}
void PercDown(ElementType A[], int p, int N)
//将N个元素的数组以A[p]为根的子堆调整为最大堆
//改变代码4.24的PercDown(MaxHeap H, int p)(数据结构 陈越)
{
int parent, child;
ElementType X = A[p];
for (parent = p; (parent*2 + 1) < N; parent = child){
child = parent *2 +1;
if (child + 1 < N && A[child + 1] > A[child])
child ++;
if (X >= A[child])//找到了合适位置
break;
else
A[parent] = A[child];
}
A[parent] = X;//parent即为X的位置
}
int main()
{
int a[] = {21, 24, 56, 87, 90, 76};
HeapSort(a, 6);//从小到大排序
for (int i = 0; i < 6; i++){
if (i == 5)
printf("%d\n",a[i]);
else
printf("%d ",a[i]);
}
system("pause");
return 0;
}
- 堆排序的小应用:
1万个数里面找最大的10个数,只需要一个大小为10的最小堆;
从第11个数开始,每次输入一个数,比堆顶大的话,就替换掉堆顶元素;
这样1w个数下来,最小堆中就保存了最大的10个数字;
复杂度是, 或者说其中是一个远比N小的常数。
归并排序
- 算法核心:两个有序子列的归并。
- 递归算法:核心思想
分而治之。- 递归实现代码:
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
//L = 左边起始位置, R = 右边起始位置, RightEnd = 右边终点位置
void Merge(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd)//两个有序子列的归并
//将两个*有序*的子列A[L]-A[R-1]和A[R]-A[RightEnd]归并成一个有序序列
{
int LeftEnd = R - 1;//左边终点位置
int temp = L;//有序序列的起始位置
int ElementNum = RightEnd - L + 1;//两个有序子列总元素个数
while (L <= LeftEnd && R <= RightEnd){
if (A[L] < A[R])
TmpA[temp++] = A[L++];
else
TmpA[temp++] = A[R++];
}
while (L <= LeftEnd)
TmpA[temp++] = A[L++];//直接复制左边剩下的
while (R <= RightEnd)
TmpA[temp++] = A[R++];//直接复制右边剩下的
for (int i = 0; i < ElementNum; i++, RightEnd--){
//将有序的TmpA[]复制为A[]
A[RightEnd] = TmpA[RightEnd];
}
}
void MSort(ElementType A[], ElementType TmpA[], int L, int RightEnd)//递归实现归并排序,此处TmpA设置是个重要问题
//中分递归具有较好的时间复杂度
{
int center;
center = (L + RightEnd) / 2;
if (L < RightEnd){
MSort(A, TmpA, L, center); //递归解决左边
MSort(A, TmpA, center + 1, RightEnd); //递归解决右边
Merge(A, TmpA, L, center + 1, RightEnd); //合并两个有序序列
}
}
void MergeSort(ElementType A[], int N)//统一函数接口
{
//定义TmpA[]
ElementType *TmpA;
TmpA = (ElementType *)malloc(sizeof( ElementType) * N);
if (TmpA != NULL){
MSort(A, TmpA, 0, N - 1);
free(TmpA);
}
else printf("空间不足!");
}
int main()
{
int a[] = {21, 24, 56, 87, 90, 76};
MergeSort(a, 6);//从小到大排序
for (int i = 0; i < 6; i++){
if (i == 5)
printf("%d\n",a[i]);
else
printf("%d ",a[i]);
}
system("pause");
return 0;
}
- 非递归算法:充分利用TmpA[],先将A[]两个相邻子序列归并完成放入TmpA[]中,然后在TmpA[]中将A[]中归并过来的两个子列进行归并放入A[]中,如此循环……
- 非递归实现代码:
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
//L = 左边起始位置, R = 右边起始位置, RightEnd = 右边终点位置
void Merge_1(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd)//两个有序子列的归并
//将两个*有序*的子列A[L]-A[R-1]和A[R]-A[RightEnd]归并成一个有序序列
{
int LeftEnd = R - 1;//左边终点位置
int temp = L;//有序序列的起始位置
int ElementNum = RightEnd - L + 1;//两个有序子列总元素个数
while (L <= LeftEnd && R <= RightEnd){
if (A[L] < A[R])
TmpA[temp++] = A[L++];
else
TmpA[temp++] = A[R++];
}
while (L <= LeftEnd)
TmpA[temp++] = A[L++];//直接复制左边剩下的
while (R <= RightEnd)
TmpA[temp++] = A[R++];//直接复制右边剩下的
//此处与merge不同的是,将有序TmpAp[]复制回A[]这一步省去
}
void Merge_pass(ElementType A[], ElementType TmpA[], int N, int length)
//length = 当前有序子列的长度
//两两归并相邻的有序子列
{
int i, j;
for (i = 0; i <= N - 2*length; i += 2*length)
Merge_1(A, TmpA, i, i + length, i + 2*length - 1);
if (i + length < N)
//归并最后两个子列
Merge_1(A, TmpA, i, i + length, N - 1);
else//还剩最后一个子列
for (j = i; j < N; j++) TmpA[j] = A[j];
}
void MergeSort(ElementType A[], int N)//统一函数接口
{
//定义TmpA[]
ElementType *TmpA;
TmpA = (ElementType *)malloc(sizeof( ElementType) * N);
int length = 1;//初始化子列的长度
if (TmpA != NULL){
while (length < N){
Merge_pass(A, TmpA, N, length);
length *= 2;
Merge_pass(TmpA, A, N, length);
length *= 2;
}
free(TmpA);
}
else printf("空间不足");
}
int main()
{
int a[] = {21, 24, 56, 87, 90, 76};
MergeSort(a, 6);//从小到大排序
for (int i = 0; i < 6; i++){
if (i == 5)
printf("%d\n",a[i]);
else
printf("%d ",a[i]);
}
system("pause");
return 0;
}
Reference
数据结构学习笔记排序 (冒泡、插入、希尔、堆排序、归并排序)
数据结构学习笔记06排序 (冒泡、插入、希尔、堆排序、归并排序)
数据结构