目录
一 插入排序
1.直接插入排序
1.1基本思想
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐 个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。1.2代码实现
void InsertSort(int* a, int n) { int i = 0; for (i = 0;i < n - 1;i++) { //end最多为n-2,如果为n-1 ,下标会越界 int end = i; //将[0,end]有序 将end+1位置插入进去 使得[0,end+1]变为有序 int tmp = a[end + 1]; while (end >= 0) { if (a[end] > tmp) { a[end + 1] = a[end]; end--; } else { break; } } a[end + 1] = tmp; } }
1.3图解分析
![]()
1.4特性
1. 元素集合越接近有序,直接插入排序算法的时间效率越高2. 时间复杂度: O(N^2)3. 空间复杂度: O(1) ,它是一种稳定的排序算法4. 稳定性:稳定
2.希尔排序
2.1基本思想
希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有数据分成各个组,所有距离为的数据分在同一组内,并对每一组内的数据进行排序。然后重 复上述分组和排序的工作。当分组的距离达到1时,所有数据在同一组内排好序。2.2代码实现
void ShellSort(int* a, int n) { int gap = n; while (gap > 1) { // pag = pag / 2; gap = gap / 3 + 1; // gap > 1时都是预排序 接近有序 // gap == 1时就是直接插入排序 有序 // gap很大时,下面预排序时间复杂度O(N) // gap很小时,数组已经很接近有序了,这时差不多也是O(N) //间隔为pag的多组排序同时进行 for (int i = 0;i < n - gap;i++) { int end = i; int tmp = a[end + gap]; while (end >= 0) { if (a[end] > tmp) { a[end + gap] = a[end]; end -= gap; } else { break; } } a[end + gap] = tmp; } } }
2.3图解分析
2.4特性
1. 希尔排序是对直接插入排序的优化。2. 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的 了,这样就会很快。这样整体而言,可以达到优化的效果。3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3—N^2)4. 稳定性:不稳定
二 选择排序
1.直接选择排序
1.1基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全 部待排序的数据元素排完 。1.2代码实现
void SelectSort(int* a, int n) { int begin = 0, end = n - 1; while(begin<end) { int mini = begin; int maxi = begin; for (int i=begin;i<=end;i++) { if (a[i] < a[mini]) mini = i; if (a[i] > a[maxi]) maxi = i; } Swap(&a[begin], &a[mini]); //begin与maxi重合的处理 if (begin== maxi) { maxi = mini; } Swap(&a[maxi], &a[end]); begin++; end--; } }
1.3特性
1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用2. 时间复杂度: O(N^2)3. 空间复杂度: O(1)4. 稳定性:不稳定2.堆排序
2.1基本思想
堆排序 (Heapsort) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的 一种。它是通过堆来进行选择数据。 需要注意的是排升序要建大堆,排降序建小堆 。2.2相关概念图解
2.3向下调整算法
2.4 图解分析
2.5代码实现
//向下调整算法 void AdjustDwon(int* a, int n, int root) { //条件 左右子树都为小堆 int parent = root; //默认为左 int child = parent * 2 + 1; while (child < n) { //左右哪一个孩子小 //child + 1 < n 如果child为最后一个元素 child+1 会越界 if (child + 1 < n && a[child + 1] < a[child]) { child++; } if (a[child] < a[parent]) { Swap(&a[parent], &a[child]); parent = child; child = parent * 2 + 1; } else { break; } } } //堆排序 void HeapSort(int* a, int n) { //从末端开始 建堆 O(N) for (int i = (n - 1 - 1) / 2;i >= 0;i--) { AdjustDwon(a, n, i); } //排降序,建大堆还是小堆?建小堆 int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); AdjustDwon(a, end, 0); end--; } }
2.6特性
1. 堆排序使用堆来选数,效率就高了很多 logN。2. 时间复杂度: O(N*logN)3. 空间复杂度: O(1)4. 稳定性:不稳定
三 交换排序
1.冒泡排序
1.1.代码实现
void BullSort(int* a, int n) { int i, end = n - 1; while (end>0) { //优化 int chanxn = 0; for (i = 1;i <= end;i++) { if (a[i - 1] > a[i]) { Swap(&a[i - 1], &a[i]); chanxn = 1; } } if (chanxn == 0) { break; } end--; } }
1.2特性
1. 冒泡排序是一种非常容易理解的排序2. 时间复杂度: O(N^2)3. 空间复杂度: O(1)4. 稳定性:稳定2.快速排序
2.1基本思想
快速排序是 Hoare 于 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序 元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有 元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所 有元素都排列在相应位置上为止。2.2挖坑法图解
2.3挖坑法代码实现
//挖坑法 int PartSort1(int* a, int left, int right) { int begin = left, end = right; int pivot = begin; int key = a[begin]; while (begin < end) { //右边找小 while (a[end] >= key && begin < end) { end--; } a[pivot] = a[end]; pivot = end; while (a[begin] <= key && begin < end) { begin++; } a[pivot] = a[begin]; pivot = begin; } pivot = begin; a[pivot] = key; return pivot; } void QuickSort(int* a,int left,int right) { // 区间不存在或者只有一个元素 if (left >= right) { return; } int pivot = PartSort3(a,left,right); // [left,pivot-1] pivot [pivot+1,right] QuickSort(a, left, pivot - 1); QuickSort(a, pivot + 1, right); }
2.4双指针法图解
2.5双指针法代码实现
//双指针法 int PartSort2(int* a, int left, int right) { int begin = left, end = right; int keyi = begin; while (begin < end) { while (a[end] >= a[keyi]&& begin < end) { end--; } while (a[begin] <= a[keyi] && begin < end) { begin++; } Swap(&a[end], &a[begin]); } Swap(&a[keyi], &a[end]); return end; } void QuickSort(int* a,int left,int right) { if (left >= right) { return; } int pivot = PartSort3(a,left,right); // [left,pivot-1] pivot [pivot+1,right] QuickSort(a, left, pivot - 1); QuickSort(a, pivot + 1, right); }
2.6前后指针法图解
2.7前后指针法代码实现
//前后指针法 int PartSort3(int* a, int left, int right) { int keyi = left; int prev = left, cur = left + 1; while (cur <= right) { if (a[cur] < a[keyi] && ++prev != cur) { Swap(&a[prev], &a[cur]); } ++cur; } Swap(&a[keyi], &a[prev]); return prev; } void QuickSort(int* a,int left,int right) { if (left >= right) { return; } int pivot = PartSort3(a,left,right); // [left,pivot-1] pivot [pivot+1,right] QuickSort(a, left, pivot - 1); QuickSort(a, pivot + 1, right); }
2.8优化
小区间优化以及三数取中
2.9代码实现
//三数取中 int GetMidIndex(int* a, int left, int right) { int mid = (left + right) >> 1; if (a[left] > a[mid]) { if (a[right] > a[left]) return left; else if (a[mid] > a[right]) return mid; else return right; } else //a[left] <= a[mid] { if (a[right] < a[left]) return left; else if (a[mid] < a[right]) return mid; else return right; } } void QuickSort(int* a,int left,int right) { if (left >= right) { return; } int pivot = PartSort3(a,left,right); // [left,pivot-1] pivot [pivot+1,right] //小区间优化 if (pivot - 1 - left >10) { QuickSort(a, left, pivot - 1); } else { InsertSort(a + left, pivot - 1 - left+1); } if (right - (pivot+1) > 10) { QuickSort(a, pivot + 1, right); } else { InsertSort(a+ pivot + 1, right - (pivot + 1)+1); } }
2.10代码易错分析图解
2.11特性
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫 快速 排序2. 时间复杂度: O(N*logN)3. 空间复杂度: O(logN)4. 稳定性:不稳定
四 归并排序
1.归并排序
1.1基本思想
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法(Divide and Conquer )的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序 列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序也叫 外排序 ,可以对磁盘上的数据进行排序。1.2图解分析
1.3代码实现
//归并子函数 void _MergeSort(int* a, int left, int right, int* tmp) { if (left >= right) return; int mid = (left + right) >> 1; // 假设 [left, mid] [mid+1, right] 有序,那么我们就可以归并了 _MergeSort(a, left, mid, tmp); _MergeSort(a, mid+1, right, tmp); // 归并 int begin1 = left, end1 = mid; int begin2 = mid + 1, end2 = right; int index = left; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[index++] = a[begin1++]; } else { tmp[index++] = a[begin2++]; } } //将剩余数据拷贝到tmp中 while (begin1 <= end1) { tmp[index++] = a[begin1++]; } while (begin2 <= end2) { tmp[index++] = a[begin2++]; } // 拷贝回去 for (int i = left; i <= right; ++i) { a[i] = tmp[i]; } } void MergeSort(int* a, int n) { int* tmp = (int*)malloc(sizeof(int)*n); _MergeSort(a, 0, n - 1, tmp); free(tmp); }
1.4特性
1. 归并的缺点在于需要 O(N) 的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。2. 时间复杂度: O(N*logN)3. 空间复杂度: O(N)4. 稳定性:稳定
五 非递归实现排序
在前面快速排序以及归并排序都使用递归实现的,然而递归存在一定的缺陷,最为主要的就是当递归的深度足够深的时候,会发生栈溢出。
改为非递归的两种方式,一是改循环,二是借用数据结构的栈,队列等来模拟递归的过程。
5.1快速排序的非递归实现(栈)
void QuickSortNonR(int* a,int n)
{
ST st;
StackInit(&st);
//右
StackPush(&st, n - 1);
//左
StackPush(&st, 0);
while (!StackEmpty(&st))
{
int left = StackTop(&st);
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int pivot = PartSort1(a, left, right);
// [left pivot-1] pivot [pivot+1 right]
//判断左右区间是否需要入栈
//右
if (pivot + 1 < right)
{
StackPush(&st, right);
StackPush(&st, pivot + 1);
}
//左
if (left < pivot - 1)
{
StackPush(&st, pivot - 1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
5.2快速排序的非递归实现(队列)
void QuickSortNonRQueue(int* a, int n)
{
Queue q;
QueueInit(&q);
//左
QueuePush(&q, 0);
//右
QueuePush(&q, n - 1);
while (!QueueEmpty(&q))
{
int left = QueueFront(&q);
QueuePop(&q);
int right = QueueFront(&q);
QueuePop(&q);
int pivot = PartSort2(a, left, right);
// [left pivot-1] pivot [pivot+1 right]
//左
if (left < pivot - 1)
{
QueuePush(&q, left);
QueuePush(&q, pivot - 1);
}
//右
if (pivot + 1 < right)
{
QueuePush(&q, pivot+1);
QueuePush(&q,right);
}
}
//销毁
QueueDestory(&q);
}
5.3归并排序的非递归实现(循环)
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
int gap = 1;
while (gap < n)
{
for (int i = 0;i < n;i += 2 * gap)
{
//[i i+gap-1] [i+gap i+2*gap-1]
//归并
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//右半区间不存在
if (begin2 >= n)
break;
//归并的时候右半区间算多了
if (end2>=n)
{
end2 = n - 1;
}
int index = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
//归并多少拷贝多少
for (int j = i;j <= end2;j++)
{
a[j] = tmp[j];
}
}
gap *= 2;
}
free(tmp);
}
六 其余排序
6.1计数排序
void CountSort(int* a, int n)
{
int max = a[0], min = a[0];
for (int i = 0;i < n;i++)
{
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
memset(count, 0, sizeof(int) * range);
//统计次数
for (int i = 0;i < n;i++)
{
//相对位置
count[a[i] - min]++;
}
//排序
int j = 0;
for (int i = 0;i < range;i++)
{
while (count[i]--)
{
a[j] = i + min;
j++;
}
}
free(count);
}
计数排序的时间复杂度为O(N+range),也就是说明它适用于范围集中的一组整形数据排序,当range较大的话,速度就比较慢了。
稳定性
排序结束后相同数据的相对位置不会发生改变