三个重要的排序:快排 + 堆排 + 归并
快排
十分注意:快排是建立在三数取中法之上的,没有三数取中的快排什么都不是。
时间复杂度:O(n*log n);
另外:快排有许多注意的细节,详见代码注释关键字 的位置也会影响结果。
#include <stdio.h>
void Swap(int* a, int* b)
{
int tmp =*a;
*a = *b;
*b = tmp;
}
int GetMid(int* a, int begin, int end)
{
int mid = begin +( (end - begin) >> 1);
if(a[begin] > a[mid])
{
if(a[mid] > a[end])
return mid;
if(a[begin] > a[end])
return a[end];
else
return begin;
}
else // a[begin] < a[mid]
{
if(a[begin] >a[end])
return begin;
if(a[mid] < a[end])
return mid;
else
return end;
}
}
//内部一趟 end 做关键字
int PartQsort(int* a,int begin, int end)//partsort 返回 key 的位置
{
//end 作关键字
int mid =GetMid(a,begin,end);//三数取中法 ,防止 最坏时间复杂度,即 防止数据有序 :思想:将有序数组先打乱,拿到中位数 做key 再排序
Swap(&a[end], &a[mid]);
int key = end;//注意: 选关键字的位置 决定了 先从哪边开始,选关键字为end,先从 左开始, 选关键为begin, 先从 右开始
//为什么 ? 开始的方向 决定了 相遇点的数据 跟key的交换 ,而key 又决定了 开始走的方向
while(begin < end)
{
//end 做key
//左边 先走
while(begin < end && a[begin] <= a[key]) //begin 找大
{
++begin;
}
//右边后走
while(begin < end && a[end] >= a[key]) //end找小
{
--end;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[key] , &a[end]);//begin == end时 将a[begin] 和 a[key] 交换
return begin;
//return end;一样
}
//begin 做关键字
int PartQSort(int* a, int begin, int end)
{
int key = begin;
int mid =GetMid(a, begin, end);
Swap(&a[key], &a[mid]);
while(begin < end)
{
while(begin < end && a[end] >= a[key])//begin 做关键字 右边先走, 右边找小于a[key]的
{
--end;
}
while(begin < end && a[begin] <= a[key] ) // 左边后走,找大于a[key]的
{
++begin;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[key], &a[begin]);
return begin;//返回交换后关键字的位置//或者相遇点的位置
}
//单趟复杂度, O(n);
//外部大循环
void Qsort(int* a, int left, int right) //递归 重复进行PartQSort
{
if(left >= right)//因为 right = keyIndex-1 ;所以 left 也可能大于 right
return;
//int keyIndex = PartQsort(a,left, right);
int keyIndex = PartQSort(a,left, right);
QSort(a, left, keyIndex -1);
QSort(a,keyIndex + 1, right);
// PartQsort(a, left, keyIndex -1);
// PartQsort(a,keyIndex + 1, right);
}
//看了外部大循环 类似于二叉树的递归 ,所以时间复杂度为O(log N)
//最坏情况 有序 ,还要排,这样就会一直找,所以最坏 0(n*n) 即 ,每次都取了最末端
//最好情况 每次 keyIndex 都选中了中位数 O(n*logN)
//有序排序 最坏 ,那么快排怎么避免 最坏情况? 三数取中法 ,即 关键字取中间的那个数
int main()
{
int a[] ={49, 38, 65, 97, 76, 13, 27, 49};
int n = sizeof(a) / sizeof(int);
Qsort(a,0,n-1);
for(int i=0; i<n; ++i)
{
printf("%d\t",a[i]);
}
printf("\n");
return 0;
}
2 挖坑法:
partSort2:
void PartSort2(int* a, int begin, int end)
{
int mid = GetMid(int*a , int begin, int end);
Swap(&a[mid], &a[begin]);//key在begin 就 交换a[begin] , key 在 end 就 交换 a[end];
int key = a[begin];
//左边做key
while(begin < end)
{
while(begin < end && a[end] >= key])
{
--end;
}
a[begin] = a[end];// end 替代begin成为新坑
while(begin < end && a[begin] <= key )//找大于
{
++begin;
}
}
a[begin] = key; //begin==end 填坑
}
挖坑法 实质跟上边那个一样。
3 前后指针法:
我只写了 内层的排序 ,外层还是递归一趟一趟。
key 必须选最右边的
int Part3(int* a, int begin ,int end)
{
int key = end;
int key = cur;
int prev = begin -1;
whlie( cur < key)
{
if (a[cur] < a[key] ) //比 关键字小
{
++prev;
Swap(&a[prev, &a[cur]]);
++cur;
}
else //比关键字大
{
++cur;
}
}
Swap(&a[++prev], &a[key]);
return prev;
}
代码优化:
```c
int part3(int* a, int begin, int end)
{
int key = end;
int prev = begin -1;
int cur =begin;
while(cur < end)
{
if( a[cur] < a[key] && ++prev != cur)
// ++prev == cur 时 ,交换也没效果
Swap(&a[prev], &a[cur]);
++cur;
}
Swap(&a[++prev], &a[key]);
return prev;
}
堆排序
#include <stdio.h>
void Swap(int* a, int* b)
{
int tmp =*a;
*a = *b;
*b =tmp;
}
void AdjustDown(int* a,int n ,int root)
{
int parent = root;
int child = parent * 2 + 1;
while(child <n)
{
if( child + 1< n && a[child] < a[child+1] )//child + 1 防止越界
{
++child;
}
if( a[child] > a[parent] )//孩子大于父亲 则进行 交换
{
Swap(&a[child], &a[parent]);
parent =child;
child = parent * 2 + 1;
}
else // 大孩子小于父亲 ,结束
{
break;
}
}
}
void Heap(int* a,int n)
{
//建堆
for(int i= (n-1-1) /2; i >= 0; --i)// (n-1)/2 为 最后一个非叶子节点的公式
{
AdjustDown(a,n,i);
}
//现在 我们建好了一个大根堆
//排序 升序
int end = n-1;
while(end >= 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a,end,0);
--end;
}
}
void Print(int* a,int n)
{
for(int i =0; i<n; ++i)
{
printf("%d\t",a[i]);
}
printf("\n");
}
//大堆
int main()
{
int a[9] ={2,3,76,3,45,3,6,65,676};
int n =sizeof(a) / sizeof(int);
Heap(a,n);
Print(a,n);
return 0;
}
堆排序 经常被用在求解Top K问题 ;
例如 : 荣耀战力 全国排名前十的 李白
这时我们找最大的前十个 ,所以 我们建小堆,
建小堆可以让大于堆顶的数据进入堆,进行堆顶元素出堆,新元素进队,(出堆内部蕴含AdjustDown和 进堆蕴含 AdjustUp算法);
为什么不建大堆,大堆导致只有比堆顶数据大的元素才能进堆,堆顶元素出堆之后 ,是不是不对了? 细想下,这样只能保证拿出一个最大的元素。
相反 找战力最低的是玩家,建大堆,
数据小于堆顶 ,进堆(堆顶出堆,新元素入堆,向上向下调整)
小数据就可以
核心: 我们找到的元素 ,最后都是进行与堆的最后一个元素交换的方式出堆的
3。归并排序
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void Merge(int *a, int begin, int end ,int* tmp)
{
if(begin >= end)
return;
int mid = begin + ((end - begin)>>1);
Merge(a, begin, mid, tmp);
Merge(a, mid+1, end, tmp);
int begin1 = begin;
int begin2 = mid + 1;
int end1 = mid ;
int end2 = end;
int index = begin;
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++];
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin +1));//注意 每层递归只交换自己处理的数据
}
void Print(int*a, int n)
{
int i =0;
for( ; i<n; ++i )
{
printf("%d\t",a[i]);
}
printf("\n");
}
void Test()
{
int a[] = {45,6,7,8,0,9,7,65};
int n =sizeof(a) / sizeof(int);
int tmp[n];
Merge(a, 0, n-1,tmp);
Print(a, n - 1);
}
int main()
{
Test();
return 0;
}