目录
前言
由于学校的离谱安排以及自己的拖延症,这博客有好长一段时间没有更新了,所以攒了个大的
一、插入排序
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
void InsertSort(int* a, int n) {
for (int i = 1; i < n; i++) {
int end = i - 1;
int tmp = a[i];
while (end>=0)
{
if (a[end] > tmp) {
a[end + 1] = a[end];
end--;
}
else {
break;
}
}
a[end + 1]=tmp ;
}
}
稳定性:稳定
ps:虽然最好理解但是这玩意是真的慢
二、希尔排序
在插入排序的基础上,将数组分组然后大步排序最后缩小这一个个分组,就是希尔排序的思路
(相对于插入排序,可以较快的将特大数从前往后移动,反之同理)
先简单处理增量序列:增量序列gap=(gap/3+1),gap为要排序的数组数据个数
即:先将要排序的一组记录按某个增量gap(gap/3,gap为要排序数的个数)分成若干组子序列,每组中记录的下标相差gap/3.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(gap/3)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
希尔排序可以认为是插入排序的优化
void ShellSort(int* a, int n) {
int gap = n;
while (gap>1) {
gap = gap / 3 + 1;
for (int i =0; i < n-gap; i++) {
int end = i;
int tmp = a[i+gap];
while (end >= 0)
{
if (a[end] > tmp) {
a[end + gap] = a[end];
end=end-gap;
}
else {
break;
}
}
a[end + gap] = tmp;
}
}
}
稳定性:不稳定
三、选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的
数据元素排完.
实现方法:
1,在数组[0,n]中选取最小的数,
2,交换数据,最小数放在左边,
3,在[1,n-1]再次选取,交换,缩减,直到集合剩一个元素
void SelectSort(int* a, int n)
{
int left = 0, right = n - 1;
while (left < right)
{
int min = left;
int max =left;
for (int i = left + 1; i <= right;i++) {
if (a[i] < a[min]) {
min = i;
}
if (a[i] > a[max]) {
max = i;
}
}
Swap(&a[left], &a[min]);
if (left == max)
{
max = min;
}
Swap(&a[right], &a[max]);
left++;
right--;
}
}
好理解到不想去解释了,费劲是真费劲
稳定性:不稳定
四、选择排序-堆排序(Heap sort)
排序(HeaoSort)是基于数据结构堆设计的一种排序算法,通过堆来选择数据,向上(向下)调整,得到小数(大数),然后再与堆底数据进行交换,即可排序,需要注意的是排升序建大堆,排降序建小堆
实现方法:
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序
void AdjustDwon(int* a, int n, int root) {
int parent = root;
int child = 2 * parent + 1;
while (child < n) {
if (child + 1 < n && a[child + 1] > a[child]) {
++child;
}if (a[child] > a[parent]) {
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else {
break;
}
}
}
void HeapSort(int* a, int n) {
for (int i = (n - 2) / 2; i >= 0; i--)
{
AdjustDwon(a, n - 1, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
end--;
AdjustDwon(a, end, 0);
}
}
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
ps:用c写现场建堆是真累
稳定性:不稳定
五、交换排序-冒泡排序
这个排序是好多学校最先开始教的排序了吧(这玩意不会的情况真少说实话)
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。就像水泡从杯底上升一样~
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; j++) {
bool ex = false;
for (int i = 1; i < n - j; i++) {
if (a[i - 1] > a[i]) {
Swap(&a[i - 1], &a[i]);
ex = true;
}
}
if (!ex)
{
break;
}
}
}
稳定性:稳定
六、快速排序
这玩意可废了我不少功夫,思路也理解了很久
在这之前先搞个递归(三种思路大同小异,递归思路一样,只有比较方面有区别)
ps:因为大量的顺序数组会把快排直接搞崩所以到一定范围后选择用插入排序补充
void QuickSort1(int* a, int left, int right)
{
if (left >= right)
{
return;
}int keyi = 0;
if ((right - left + 1) > 50)
{
keyi = PartSort3(a, left, right);
QuickSort1(a, left, keyi - 1);
QuickSort1(a, keyi + 1, right);
}
else {
InsertSort(a + left, right - left + 1);
}
}
这玩意分为3种写法1. hoare版本
2. 挖坑法
3. 前后指针版本
首先是
1、hoare版
经典且原始
左边值设为key,然后右边先走,找小的,比key小,然后左边走找比key大,然后交换左边右边,
int PartSort1(int* a, int left, int right)
{
int keyi = left;
while (left < right)
{
// 找小
while (left < right && a[right] >= a[keyi])
--right;
// 找大
while (left < right && a[left] <= a[keyi])
++left;
swap(&a[left], &a[right]);//交换左右值
}
swap(&a[keyi], &a[left]);//最后交换key与left
return left;//返回当前节点,[0,left-1],[left+1,right]递归排序
2、挖坑法
其设定key数组第一个值为坑,右边找下,左边找大,找到一个,交换,形成新的坑,最后把key放到坑里
int Quick1Sort1(int* a, int left, int right)
{
if (left >= right)return;
int e = right;
int begin = left;
随机选key
/*int randi = left + (rand() % (right - left));
Swap(&a[left], &a[randi]);*/
// 三数取中
int midi = GetMidNumi(a, left, right);
if (midi != left)
Swap(&a[midi], &a[left]);
int keyi = left;
int key = a[keyi];
int ken = left;
while (left < right)
{
// 右边找小
while (left < right && a[right] >= a[keyi])
right--;
a[ken] = a[right];
ken = right;
// 左边找大
while (left < right && a[left] <= a[keyi])
left++;
a[ken] = a[left];
ken = left;
}
a[ken] = key;
return ken;
}
因为选key对排序时长有大影响所以我们选择三数取中
int GetMidNumi(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
{
return right;
}
}
else // a[left] > a[mid]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
}
3、前后指针法
选取key值,cur小于key值,prev++,交换cur与prev值,
int PartSort3(int* a, int left, int right)
{
// 三数取中
int midi = GetMidNumi(a, left, right);
if (midi != left)
Swap(&a[midi], &a[left]);
int key = a[left], fast = left + 1, slow = left, keyi = left;
while (fast <= right)
{
if (a[fast] <= key) {
slow++;
if (fast != slow)
{
Swap(&a[fast], &a[slow]);
}
}
fast++;
}Swap(&a[slow], &a[left]);
return slow;
}
番外、非递归实现
通过使用栈对数据进行排序,递归本身就是一个压栈的过程
void QuickSortNonR(int* a, int left, int right)
{
Stack b;
STInit(&b);
stackpush(&b,right);
stackpush(&b,left );
while (!stackEmpty(&b))
{
int begin = stackTop(&b);
stackPop(&b);
int end = stackTop(&b);
stackPop(&b);
int keyi = PartSort3(a,begin,end);
if (keyi + 1 < end) {
stackpush(&b,end);
stackpush(&b,keyi+1);
}if (keyi - 1 > begin) {
stackpush(&b,keyi-1);
stackpush(&b, begin);
}
}stackFree(&b);
}
稳定性:不稳定
七、归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。通过递归实现对小数组有序,再返回回来,
void MergeSort(int* a, int n)
{
int* temparr = (int*)malloc(n * sizeof(int));
if (temparr != NULL)
{
msort(a, temparr, 0, n - 1);//进行排序
free(temparr);
}
else
{
printf("malloc fail\n");
}
}
void msort(int* a, int* arr, int left, int right)
{
//如果只有一个元素,默认有序,只需被归并即可
if (left < right)
{
//找中间点
int mid = (left + right) >> 1;
//对左半区进行归并
msort(a, arr, left, mid);
//对右半区归并
msort(a, arr, mid + 1, right);
//对已经排序的数组进行合并
merge(a, arr, left, mid, right);
}
}
void merge(int* a, int* arr, int left, int mid, int right)
{
//标记左半区第一个未排序的元素
int l_pos = left;
//标记右半区第一个未排序的元素
int r_pos = mid + 1;
//临时数组下标的元素
int pos = left;
//合并
while (l_pos <= mid && r_pos <= right)
{
if (a[l_pos] < a[r_pos])
arr[pos++] = a[l_pos++];
else
arr[pos++] = a[r_pos++];
}
//合并左半区剩余的元素
while (l_pos <= mid)
{
arr[pos++] = a[l_pos++];
}
//合并右半区剩余的元素
while (r_pos <= right)
{
arr[pos++] = a[r_pos++];
}
//把临时数组合并后的元素复制到a中
while (left <= right)
{
a[left] = arr[left];
left++;
}
}
稳定性:稳定
八、计数排序
运用哈希表将数组换成哈希表的一个个数值
(但是吧这玩意只适用于自然数负数浮点数全不行!)
void CountSort(int* a, int n)
{
int max = a[0], min = a[0];
for (int i = 0; i < n; ++i)
{
if (a[i] > max)
max = a[i];
if (a[i] < min)
min = a[i];
}
int range = max - min + 1;
int* count = malloc(sizeof(int)*range);
memset(count, 0, sizeof(int)*range);
for (int i = 0; i < n; ++i)//计数
{
count[a[i] - min]++;
}
int i = 0;
for (int j = 0; j < range; ++j)//排序
{
while (count[j]--)
{
a[i++] = j + min;
}
}
free(count);
}
稳定性:我都换新数组重放进去了怎么可能稳定
后话:
这些东西真的够长够难懂的有一说一,但是理解以后豁然开朗的感觉也意外很棒