排序:把一堆记录,按照其中某个或某些关键字的大小,递增或递减的排列起来的操作
稳定性:即经过排序后,记录中相同的关键字是否或保持原来的位置不做调整,如果位置不变则稳定,反之。
内部排序:数据元素在内存中的排序。
外部排序:数据元素太多了,不能同时放在内存中,根据排序要求不能在内存之间移动数据的排序。
直接插入排序 希尔排序
1.直接插入排序
基本思想:把待排序的记录按其关键码值的大小逐个插入到一 个已经排好序的有序序列中,
直到所有的记录插入完为止,得到一个新的有序序列 。
即:依次把无序数组中的数插放到数组开头(有序数组)。
[0, i - 1] -----> [i + 1, size - 1]
特性:
时间复杂度:数据敏感
最好:O(n) 平均:O(n^2) 最坏:O(n^2)
空间复杂度:O(1)
稳定的排序算法.
C语言实现:
#include "Sort.h"
void InsertSort(int array[], int size)
{
//把数组中的数组依次按顺序插入到数组的最前面
//一次处理一个数,只有遍历size次,但第一个数不动,严格上只用处理size - 1次
//有序区间: [0, i - 1]
//无序区间:[i + 1, size - 1]
//处理: i
for (int i = 0; i < size; i++)
{
int key = array[i]; //先记录下要处理的i的值
int j;
for (j = i - 1; j >= 0 && array[j] > key; j--)
{
//key小于前面有序区间的数
//找合适的位置腾地方,并把i的位置占用
array[j + 1] = array[j];
}
// 把 array[i] 插入到 [0, i - 1] 的顺序表里
array[j + 1] = key;
}
}
附上二分法实现直接插入排序代码:
#include "Sort.h"
void InsertSortBS(int array[], int size) {
for (int i = 0; i < size; i++) {
// 有序 [0, i - 1] 有序
// 无序 [i, size - 1] 无序
int left = 0;
int right = i - 1;
// [left, right] 左闭右闭
while (left <= right) {
int mid = left + (right - left) / 2;
if (array[mid] == array[i]) {
left = mid + 1;
}
else if (array[mid] < array[i]) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
// left 是要插入的位置的下标
int key = array[i];
for (int k = i; k > left; k--) {
array[k] = array[k - 1];
}
array[left] = key;
}
}
2.希尔排序(分组插排)
是对直接插入排序的优化
基本思想:先选定一个整数开始,把待排序数组分成g = g/3 + 1个组,
所有距离为g的数分在同一组内,并对每一组内的数进行排序。
然后,重复上述分组和排序的工作。当到达g == 1时,数组已经接近有序的
特性:
时间复杂度: 数据敏感
最好:O(n) 平均:O(n^1.2-1.5) 最坏:O(n^2)
空间复杂度:O(1)
不稳定的排序算法
C语言实现:
#include "Sort.h"
void InsertSortWithGap(int array[], int size, int gap)
{
for (int i = 0; i < size; i++)
{
int key = array[i];
int j;
for (j = i - gap; j >= 0 && array[j] > key; j -= gap) //
{
array[j + gap] = array[j];
}
array[j + gap] = key;
}
}
void ShellSort(int array[], int size)
{
int gap = size;
while (1)
{
//开始分组插排
gap = gap / 3 + 1;
//gap = gap / 2;
InsertSortWithGap(array, size, gap);
if (gap == 1) //直到gap == 1退出
{
break;
}
}
}
直接选择排序、堆排序
3.直接选择排序
基本思想:每次从待排序的区间中,选择一个最小(排降序)或最大(排升序)的数,
放在数组的最开头或最后,直到待排序序列的数为0。
特性:
时间复杂度(数组不敏感): O(n^2)
空间复杂度:O(1)
不稳定的排序算法
C语言实现:
#include "Sort.h"
void SetectSort(int array[], int size)
{
for (int i = 0; i < size; i++)
{
//无序区间:[0, size - 1 - i]
//有序区间:[size - 1 - i, size - 1]
int minIdx = 0; //用来存储每次遍历无序区间中最大的数的下标
for (int j = 0; j <= size - 1 - i; j++)
{ //查找无序区间中的最大值,并把下标赋给minIdx
if (array[j] >= array[minIdx])
{
minIdx = j;
}
}
//把无序区间中的最大值交换放到数组的最后,为有序区间中
Swap(array, minIdx, size - 1 - i);
}
}
4.堆排序
建堆 | 向下调整 | 交换
基本思想:指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。
它是通过堆来进行选择数据。
注意:是排升序要建大堆,排降序建小堆
特性:
时间复杂度(数组不敏感): O(n*log(n))
空间复杂度:O(1)
不稳定的排序算法。
C语言实现:
#include "Sort.h"
//向下调整
void Heapify(int array[], int size, int index)
{
while (1)
{
int minIdx = 2 * index + 1;
if (minIdx >= size)
{
return;
}
int right = 2 * index + 2;
if (right < size && array[right] > array[minIdx])
{
minIdx = right;
}
if (array[minIdx] <= array[index])
{
return;
}
Swap(array, minIdx, index);
index = minIdx;
}
}
//建大堆
void CreateHeap(int array[], int size)
{
for (int i = (size - 2) / 2; i >= 0; i--)
{
Heapify(array, size, i);
}
}
//利用堆排序
void HeapSort(int array[], int size)
{
CreateHeap(array, size);
// 一次处理一个数
// 无序 [0, size - 1 - i]
// 有序 [size - i, size - 1]
for (int i = 0; i < size; i++)
{
Swap(array, 0, size - 1 - i);
// 堆的性质被破坏了
// 只有根的地方
// 要调整的是剩余无序部分的长度 size - 1 - i
Heapify(array, size - 1 - i, 0);
}
}
冒泡排序、快排
5.冒泡排序(交换排序)
基本思想:根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
特性:
时间复杂度:数据敏感
最好:O(n) 平均:O(n^2) 最坏:O(n^2)
空间复杂度:O(1)
稳定的排序算法
C语言实现:
#include "Sort.h"
//冒泡排序
void BubbleSort(int array[], int size)
{
for (int i = 0; i < size; i++)
{
int sorted = 1; //设置一个定数值,来优化排序算法
for (int j = 0; j < size - 1 - i; j++)
{
if (array[j] > array[j + 1])
{
Swap(array, j, j + 1);
sorted = 0;
}
}
//优化算法:经过遍历后,没发生交换,则已经有序,直接退出
if (sorted == 1)
{
break;
}
}
}
6.快速排序(交换排序)
快排流程:
1.确定一个基准值
选择最边上的作为基准值(选择最右边)
2.通过一种方式,遍历整个区间,和基准值进行比较,并交换:
最终使:比基准值小的数(可能包括等于)都在基准值的左边, 反之,都在右边
3.分治算法,对左右两个小区间分别重复上述动作,
直到:小区间的 size == 0 | size == 1,区间已经有序
注:第二部(Partition)
1).Hover
2).挖坑法
3).前后下标
特性:
每一层的 Partition
时间复杂度是 O(n)
空间复杂度是 O(1)
数多少个 O(n),二叉树高度个 O(n)
空间消耗在于递归调用的栈帧消耗,最终消耗的情况是二叉树的高度
二叉树的高度是 log(n) - n 在变化
最好 平均 最坏
时间复杂度 O(n * log(n)) O(n * log(n)) O(n^2)
空间复杂度 O(log(n)) O(log(n)) O(n)
稳定性: 不稳定
选择基准值方法:
1.选最边上的不好
很容易遇到最坏的情况 逆序/顺序
2.随机选择法
[left, right] rand()
有效降低最坏的情况的概率
3.三数取中 mid = (left + right) / 2
[left, right] a[left] a[mid] a[right]
选择三个数中大小是中间的一个数
随机 + 三数取中 再把基准值交换到最边上
C语言实现:
#include "Sort.h"
//1.Hover法
int Partition1(int array[], int left, int right)
{
int begin = left; // [left, begin] 保证 <= pivot
int end = right; // [end, right] 保证 >= pivot
int pivot = array[right]; // 基准值
while (begin < end)
{
while (begin < end && array[begin] <= pivot)
{
begin++; //直到找到大于基准值的数
}
while (begin < end && array[end] >= pivot)
{
end--; //直到找到小于基准值的数
}
//交换
Swap(array, begin, end);
}
//把基准值和begin的下标交换,和第一个比 pivot 大的数交换
Swap(array, begin, right);
return begin;
}
//2.挖坑法
int Partition2(int array[], int left, int right)
{
int begin = left; // [left, begin] 保证 <= pivot
int end = right; // [end, right] 保证 >= pivot
int pivot = array[right]; // 基准值
while (begin < end)
{
while (begin < end && array[begin] <= pivot)
{
begin++;
}
array[end] = array[begin];
while (begin < end && array[end] >= pivot)
{
end--;
}
array[begin] = array[end];
}
array[begin] = pivot;
return begin;
}
//3.前后下标法
int Partition3(int array[], int left, int right)
{
int d = left;
for (int i = left; i < right; i++)
{
if (array[i] < array[right])
{
Swap(array, i, d);
d++;
}
}
Swap(array, d, right);
return d;
}
void QuickSortInner(int array[], int left, int right)
{
if (left == right)
{ //相等则是size == 1的情况
return;
}
if (left > right)
{ //size == 0
return;
}
int d = Partition1(array, left, right);
QuickSortInner(array, left, d - 1);
QuickSortInner(array, d + 1, right);
}
void QuickSort(int array[], int size)
{
QuickSortInner(array, 0, size - 1);
}
快排非递归实现:
利用栈的特性来实现非递归快排,所以利用了C++栈的特性。
#include "Sort.h"
#include
void QuickSortNOR(int array[], int size)
{
std::stack S;
S.push(size - 1);
S.push(0);
while (!S.empty())
{
int left = S.top(); S.pop();
int right = S.top(); S.pop();
if (left >= right)
{
continue;
}
int d = Partition1(array, left, right);
S.push(right);
S.push(d + 1);
S.push(d - 1);
S.push(left);
}
}
7.归并排序 (多路归并)
基本思想:把一个区间看成平均切割的两个小区间,假如左右两个小区间各自有序,
左右两个小区间不一定有序,所以分治左右两个小区间直到 size == 0
|| size == 1(有序), 合并两个有序数组。
1). 平均分割区间
2). 分支处理左右两个小区间 直到 size == 0 || size == 1
3). 合并左右两个有序数组
特性:
时间复杂度:数据不敏感型
O(n*log(n))
空间复杂度: O(n)
稳定的排序算法
外部排序(海量数据排序,内存放不下)
C语言实现:
#include "Sort.h"
void Merge(int array[], int left, int mid, int right, int extra[])
{
int i = left; //区间 [left, mid)
int j = mid; //区间 [mid, right)
int k = left; //新空间
while (i < mid && j < right)
{
if (array[i] <= array[j])
{
extra[k++] = array[i++];
}
else
{
extra[k++] = array[j++];
}
}
while (i < mid)
{
extra[k++] = array[i++];
}
while (j < right)
{
extra[k++] = array[j++];
}
for (int i = left; i < right; i++)
{
array[i] = extra[i];
}
}
void MergeSortInner(int array[], int left, int right, int extra[])
{
if (left >= right)
{ //size == 0
return;
}
if (left + 1 == right)
{ //size == 1
return;
}
//1.平均分割
int mid = left + (right - left) / 2;
//2.分治两个小区间
MergeSortInner(array, left, mid, extra); //[left, mid)
MergeSortInner(array, mid, right, extra); //[mid, right)
//3.合并两个有序数组
Merge(array, left, mid, right, extra);
}
void MergeSort(int array[], int size)
{
int* extra = (int*)malloc(size * sizeof(int));
MergeSortInner(array, 0, size, extra);
free(extra);
}
归并排序非递归法:
#include "Sort.h"
void MergeSortNOR(int array[], int size)
{
int* extra = (int*)malloc(size * sizeof(int));
// 这一层,左右两个小区间的长度都是 i
for (int i = 1; i < size; i = 2 * i)
{
for (int j = 0; j < size; j = j + 2 * i)
{
int left = j;
int mid = left + i;
if (mid >= size)
{
// 没有右边的小区间,不需要合并
continue;
}
int right = mid + i;
if (right > size)
{
right = size;
}
Merge(array, left, mid, right, extra);
}
}
free(extra);
}
test.c
#include "Sort.h"
void Print(int array[], int size)
{
printf("数组中的数为:\n");
for (int i = 0; i < size; i++)
{
printf("%d\t", array[i]);
}
printf("\n");
}
void test()
{
int array[] = {9, 2, 5, 8, 1, 3, 7, 4, 6, 0};
int size = sizeof(array) / sizeof(int);
Print(array, size);
printf("排升序! ");
Sort(array, size); //一次放入排序函数测试
Print(array, size);
}