排序的稳定性
在重复数据的前提下,这两个数据是否进行交换,如果进行交换了,则不稳定;没有进行交换,则稳定。
内排和外排
内部排序(全部放在内存中) 外部排序(部分放入内存) 以内存划分
排序的分类
- 按不同排序手段:插入排序、交换排序、选择排序、归并排序、计数排序
- 按时间复杂度:简单排序 O(n^2)、先进排序 O(log)、基数排序 O(d*n)
插入排序
1.直接插入排序
(1)基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排序好的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列(扑克牌)每次从没有排序好的 插入到排序好的合适的位置
(2)时间复杂度:O(n^2) 空间复杂度:O(1) 是否稳定:稳定
(3)方法一:进行交换 一般默认第一个元素是已经排序好的
end标记要插入的元素 当end的值小于前一个时,进行交换,end--,和之前的再进行比较直到不需要交换
//方法:1.有多少张排需要插入for 2.寻找插入位置 用while end标记
void InsertSort(int *ar, int left, int right)
{
//有多少张排需要插入
for (int i = left + 1; i < right; i++)
{
//寻找插入位置
int end = i;
//疑问为什么end>left
//left一直标记的都是0号位置 end一直标记的是要插入的值 是通过i来往后调整的
while (end>left&&ar[end] < ar[end - 1])
{
Swap(&ar[end], &ar[end - 1]);
end--;//和已经排好的元素都进行比较
}
}
}
(4)方法二:借用临时空间 将需要插入的值放入临时空间中 找到需要插入的位置
void InsertSort_1(int *ar, int left, int right)
{
//有多少个元素要进行插入
for (int i = left + 1; i < right; i++)
{
int tmp = ar[i];
int end = i;
while (end < left&&tmp < ar[end - 1])
{
ar[end] = ar[end - 1];
end--;
}
//之前end的位置值都是空的
ar[end] = tmp;
}
}
(5)方法三:哨兵位的直接插入排序
思想:哨兵位a[0] 充当临时空间,并且控制越界;将待排序序列的第一个位置空出来设为哨兵位置,因此在定义序列式需要多给一个空间
void InsertSort_2(int *ar, int left, int right)
{
for (int i = left + 1; i < right; ++i)
{
ar[0] = ar[i];//哨兵位
int end = i;
while (ar[0] < ar[end - 1])
{
ar[end] = ar[end - 1];
end--;
}
ar[end] = ar[0];
}
}
2.折半插入排序
思想:将待排序的序列插入已经排序好的序列中,每次查找插入位置都是从一半查起,使用临时空间
void BinInsertSort(int *ar, int left, int right)
{
//有多少元素要插入
for (int i = left + 1; i < right; ++i)
{
//寻找插入的位置
int tmp = ar[i];
//标记已经排序好的序列的范围
int low = left;
int high = i - 1;;
while (low<=high)
{
int mid= (low + high) / 2;
if (tmp < ar[mid])
{
high = mid - 1;
}
else{
low = mid + 1;
}
}
//将空间向后移动
//high + 1就是要插入的位置
for (int j = i - 1; j >= high + 1; j--)
{
ar[j + 1] = ar[j];
}
ar[high + 1] = tmp;
}
}
3.希尔排序(缩小增量排序)
(1)思想:先将整个待排序记录序列分割成为若敢子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序
(2)快的原因:基本有序、数据量小 时间复杂度:O(n^1.3~n^2) 是否稳定:不稳定 是对直接插入排序的优化
//dk是增量
//方法:1.控制dk里的每一对数据,遍历每一对数据,也就是遍历数组
//2.每一对数据进行比较 标志其第二个位置和起始位置
//3.每一个大集合中 和前面的序列进行比较,找到合适的位置
void ShellSort(int* ar, int left, int right)
{
int dk = right - left;
while (dk > 1)
{
dk = dk / 3 + 1;
for (int i = left + dk; i < right; i++)//控制dk里的每一对数据
{
if (ar[i] < ar[i - dk])
{
int tmp = ar[i];//第二个位置
int end = i - dk;//起始位置(相对来说)
//和前面的序列进行比较(一趟中的)
while (end >= left&&tmp > ar[end])
{
ar[end + dk] = ar[end];
end -= dk;
}
ar[end + dk] = tmp;
}
}
}
}
选择排序
1.直接选择排序
(1)基本思想:每一次从待排序的元素中选出最小或最大的一个元素,存放在序列的起始位置(依次往后放),直到全部待排序的数据元素排完
(2)时间复杂度:O(n^2) 空间复杂度:O(1) 是否稳定:不稳定
//1.要排序的元素for循环 2.默认第一个元素时最小的 用pos标记最小的 3.从剩下的元素中寻找最小的(和min比较)4.进行交换,看pos是否等于i
void SelectSort(int * ar, int left, int right)
{
//要排序的元素
for (int i = left; i < right; i++)
{
//寻找需要交换的元素 从剩下的元素里找
int min = ar[i];
int pos = i;//标记最小的
for (int j = i + 1; j < right; j++)
{
if (min > ar[j])
{
min = ar[j];
pos = j;
}
}
//如果不相等 则找到后进行交换
if (pos != i)
{
Swap(&ar[i], &ar[min]);
}
}
}
2.堆排序(重要)
(1)排升序建立大堆,降序建立小堆
(2)时间复杂度:O(N*logN) 空间复杂度 O(1) 稳定性:不稳定
void _ShifDown(int *ar, int left, int right, int curpos)
{
//对该分支进行调整
int i = curpos;//父节点
int j = i * 2 + 1 + left;//子节点
int n = right - left;
while (j < n)
{
if (j + 1 < n&&ar[j] < ar[j + 1])
{
j++;
}
if (ar[j] > ar[i])
{
Swap(&ar[j], &ar[i]);
i = j;
j = i * 2 + 1;
}
else{
break;
}
}
}
void HeapSort(int *ar, int left, int right)
{
//创建大堆 先找到最后一个分支
int n = right - left;
int cur = n / 2 - 1 + left;
//大的框架 每个分支
while (cur >= 0)
{
_ShifDown(ar,left,right,cur);
cur--;
}
//排序
int end = right - 1;
while (end > left)
{
Swap(&ar[left], &ar[end]);
end--;//也就是说每次的最后一个都是最大的值
_ShifDown(ar, left, end, left);
}
}
交换排序
基本思想:根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置
交换排序特点:将键值较大的记录向序列尾部移动,键值较小的记录向序列头部移动
1.冒泡排序
(1)思想:对由n个记录组成的记录序列,最多经过(n-1)趟冒泡排序,就可以使记录序列成为有序序列;每一趟中比较相邻的元素,如果第一个比第二个大,就交换他们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对;针对所有的元素重复以上的步骤,直到没有任何一对数字需要比较。
(2)时间复杂度:O(N) 空间复杂度:O(1) 稳定性:稳定
//改进方法:加入一个标记位 用来标记此趟排序是否有交换,如果有则说明整体数据未必有序;如果 没有则说明数据已经有序了,直接退出
void BubbleSort(int*ar, int left, int right)
{
int n = right - left;
bool is_swap = false;
for (int i = left; i < n-1; i++)
{
for (int j = i; j < n - i - 1; j++)
{
if (ar[j]>ar[j + 1])
{
Swap(&ar[j], &ar[j + 1]);
is_swap = true;
}
}
if (!is_swap)
{
break;//为假时
}
else{
is_swap = false;
}
}
}
2.快速排序(重要)
(1)思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应的位置上为止。
(2)时间复杂度:O(N*logN) 空间复杂度O(logN) 稳定性:不稳定
(3)hoare版本 找到分割位置 递归的方式
int _Partition_1(int *ar, int left, int right)
{
int key = ar[left];
while (left < right)
{
while (left < right&&ar[right] >= key)
right--;
Swap(&ar[left], &ar[right]);
while (left < right&&ar[left] < key)
left++;
Swap(&ar[left], &ar[right]);
}
//left==right
return left;
}
void QuickSort(int *ar, int left, int right)
{
if (left >= right - 1)
return;
int pos = _Partition_1(ar, left, right - 1);//用于分割
QuickSort(ar, left, pos);
QuickSort(ar, pos + 1, right);
}
(4)挖坑法
int _Partition_2(int *ar, int left, int right)
{
int key = ar[left];
while (left < right)
{
while (left < right&&key <= ar[right])
right--;
ar[left] = ar[right];
while (left<right&&key>ar[left])
left++;
ar[right] = ar[left];//没明白这里 需要画图
}
ar[left] = key;
return left;
}
void QuickSort_1(int *ar, int left, int right)
{
if (left >= right - 1)
return;
int pos = _Partition_2(ar, left, right - 1);
QuickSort_1(ar, left, pos);
QuickSort_1(ar, pos + 1, right);
}
(5)前后指针法
两个指针一快一慢 当前一个指针位置的值小于key时,后一个指针向前走,走到它的位置,两者相等;当前一个指针走的位置的值大于key时,后一个指针不动,前一个指针一直向前走,直到找到小于key的位置,两个指针不相等 进行交换;前一个找比key大的,后一个找比key小的 当后面的都是大值时,key和慢指针进行交换
int _Partition_3(int *ar, int left, int right)
{
int key = ar[left];
int pos = left;
for (int i = left + 1; i <=right; i++)
{
if (ar[i] < key)
{
pos++;
if (pos != i)
Swap(&ar[pos], &ar[i]);
}
}
Swap(&ar[left], &ar[pos]);
return pos;
}
void QuickSort(int *ar, int left, int right)
{
if (left >= right - 1)
return;
int pos = _Partition_2(ar, left, right - 1);
QuickSort_1(ar, left, pos);
QuickSort_1(ar, pos + 1, right);
}
(6)改进快排
- 数据量小时用直接插入排序,反之用快排
#define M 25
int _Partition_1(int *ar, int left, int right)
{
int key = ar[left];
while (left < right)
{
while (left < right&&ar[right] >= key)
right--;
Swap(&ar[left], &ar[right]);
while (left < right&&ar[left] < key)
left++;
Swap(&ar[left], &ar[right]);
}
//left==right
return left;
}
void QuickSort_2(int *ar, int left, int right)
{
if (left >= right)
return;
if (left - right <= M)
{
//直接插入排序
//1.需要插入多少元素
//2.找到插入位置
for (int i = left + 1; i < right; i++)
{
int end = i;
while (end>left&&ar[end] < ar[end - 1])
{
Swap(&ar[end], &ar[end - 1]);
end--;
}
}
}
else{
int pos = _Partition_1(ar, left, right - 1);
QuickSort(ar, left, pos);
QuickSort(ar, pos + 1, right);
}
}
- 三者取中算法 left right mid 三者找第二大 解决最坏情况
int GetIndexMidVal(int *ar, int left, int right)
{
int mid = (left + right) / 2;
if (ar[left] > ar[mid] && ar[left] <ar[right])
return left;
if (ar[mid]>ar[left] && ar[mid] < ar[right])
return mid;
return right;
}
int _Partition_4(int *ar, int left, int right)
{
int index = GetIndexMidVal(ar, left, right);
//为了匹配下面的代码 必须在第一个位置
if (index != left)
Swap(&ar[index], &left);
int key = ar[left];
int pos = left;
for (int i = left + 1; i <= right; i++)
{
if (ar[i] < key)
{
pos++;
if (pos != i)
Swap(&ar[pos], &ar[i]);
}
}
Swap(&ar[left], &ar[pos]);
return pos;
}
归并排序
(1)思想:分治法,将序列分成多个子序列,即先使每个子序列有序,再使子序列段间有序。
(2)时间复杂度: O(NlogN) 空间复杂度:O(N) 稳定性:稳定
每次按一半分解,直到只剩下一个元素,再进行合并(顺序表的合并),两两合并
void _MergeSort(int* ar, int left, int right, int *tmp)
{
if (left >= right)
return;
//1.先分解
int mid = (left+right) / 2;
_MergeSort(ar, left, mid, tmp);
_MergeSort(ar, mid + 1, right, tmp);
//2.后归并
int begin1, end1, begin2, end2, i;
begin1 = left, end1 = mid;
begin2 - mid + 1, end2 = right;
i = 0;
while (begin1<=end1&&begin2<=end2)
{
if (ar[begin1] < ar[begin2])
{
tmp[i++] = ar[begin1++];
}
else{
tmp[i++] = ar[begin2++];
}
}
while (begin1 <= end1)
tmp[i++] = ar[begin1++];
while (begin2 <= end2)
tmp[i++] = ar[begin2++];
//拷贝到原空间
memcpy(ar + left, tmp, sizeof(int)*(left - right));
}
void MergeSort(int *ar, int left, int right)
{
int n = right - left;
int* tmp = (int*)malloc(sizeof(int)*n);
assert(tmp != NULL);
//归并排序
_MergeSort(ar, left, right - 1, tmp);
free(tmp);
tmp = NULL;
}
计数排序
(1)思想:统计相同元素出现次数 根据统计的结果将序列回收到原来的序列当中
(2)时间复杂度:O(MAX(N,范围)) 空间复杂度O(N) 稳定性:稳定
#include "slist.h"
#define RADIX 10
SList list[RADIX];
#define K 2
int GetKey(int value, int k)
{
int key;
while (k >= 0)
{
key = value%10;
value /= 10;
k--;
}
return key;
}
void Distribute(int* ar, int left, int right, int i)
{
for (int i = left; i < right; i++)
{
int key = GetKey(ar[i], k);
SListPushBack(&list[key], ar[i]);
}
}
void Collect(int *ar)
{
int k = 0;
for (int i = 0; i < RADIX; ++i)
{
while (!SListEmpty(&list[i]))
{
ar[k++] = SListFront(list[i]);
SListPopFront(&list[i]);
}
}
}
void RadixSort(int *ar, int left, int right)
{
for (int i = 0; i < RADIX; ++i)
SListInit(&list[i]);
for (int i = 0; i < K; i++)
{
//1.分发
Distribute(ar, left, right, i);
//2.回收
Collect(ar);
}
}
总结
冒泡排序、直接选择排序、直接插入排序------时间复杂度都为O(N^2)
希尔排序 -----时间复杂度为 O(NlogN)~O(N^2)
堆排序、归并排序、快速排序-----时间复杂度为 O(NlogN)
稳定:冒泡排序、直接选择排序、直接插入排序、归并排序
不稳定:希尔排序、堆排序、快速排序
冒泡排序、直接选择排序、直接插入排序、希尔排序、堆排序-----空间复杂度都为O(1)
归并排序-----空间复杂度为O(N)
快速排序-----空间复杂度为O(logN)~O(N)