//包含头文件
#include "Stack.h"
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
一.插入排序:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
时间复杂度: O(N^2)
空间复杂度: O(1)
稳定性: 稳定
适合场景: 接近有序的序列, 时间复杂度趋近于O(N)对于有序的序列, 时间复杂度O(N)
void InsertSort(int* a , int n){
assert(a);
int i;
for(i = 0;i < n-1 ;++i){
//单个元素的排序
//找到已经排好序的最好有一个元素的位置
int end = i;
//把end + 1 位置的元素插入到合适的位置
int tmp = a[end + 1];
while(end >= 0 && a[end] > tmp){
a[end + 1 ] = a[end];
--end;
}
//找到合适的位置
a[end + 1] = tmp;
}
}
二.希尔排序
先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工
作。当到达=1时,所有记录在统一组内排好序
//时间复杂度: O(N^1.3 ~ N^2)
//空间复杂度: O(1);
稳定性: 不稳定
void Shell(int* a , int n){
int gap = n;
int i;
// for(; gap >= 1; gap -= num){//预排序的次数
// for (num = 0; num < gap ; ++num) {//待排序的组别
while(gap >1){
//gap > 1 -->预排序过程
//gap = 1 -->排序过程
gap = gap / 3 + 1;// + 1保证最后一次gap为1的插入排序
for(i = 0 ; i < n - gap ; i ++){
int end = i;
int tmp = a[end + gap];
while(end >= 0 && a[end] > tmp){
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
}
}
三.选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
时间复杂度: O(N^2)
空间复杂度: O(1)
稳定性: 可以稳定
void SelectSort(int* a, int n){
int begin = 0 , end = n-1;
while(begin < end){
//每次选一个最大的和最小的, 放到对应位置
int i, max, min;
max = min = begin;
//小的选第一个, 大的选最后一个
for(i = begin; i <= end; ++i){
if(a[i] < a[min])
min = i;
if(a[i] >= a[max])//>=就稳定
max = i;
}
//min --> begin max -->end
Swap(&a[begin], &a[min]);
//判断最大元素位置是否发生变化
if(max == begin)
max = min;
Swap(&a[end] , &a[max]);
++begin;
--end;
}
}
Swap函数:
void Swap(int* x, int* y){
int tmp = *x;
*x = *y;
*y = tmp;
}
四.堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是
通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
时间复杂度:O( N*logN)
空间复杂度: O(1)
稳定性: 不稳定
//向下调整,调成大根堆 O(logN)
void ShiftDown(int* a,int n,int root){//数组个数n
//(n - 2) / 2是最后一个非叶子索引
// 0~(n - 2) / 2 所有非叶子节点索引
assert(a);
int parent = root;
int child = 2 * parent + 1;
//当前节点是否有child
while (child < n){
//是否有右child,如果有,两者当中选最大
if(child + 1 < n && a[child + 1] > a[child])
++child;
//child是否大于parent
if(a[child] > a[parent]){
//交换
Swap(&a[child] , &a[parent]);
//更新下一次调整的位置
parent = child;
child = 2 * parent + 1;
}else{
//以parent为根的子树已经是一个大堆,结束调整
break;
}
}
}
// 堆排序
void HeapSort(int* a, int n){
//建堆
//最后一棵子树开始 (n - 2) /2
int i , end;
for (i = (n - 2) / 2 ; i >= 0 ; --i) {
ShiftDown(a , n , i );
}
//堆排序
end = n - 1;
while(end > 0){//一次排序确定一个元素
Swap(&a[0], &a[end]);//交换
ShiftDown(a, end, 0);//从堆顶向下调整
--end;//排一个 堆少一个元素
}
}
五.冒泡排序
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
void BubbleSort(int* a, int n){
int cur, bound;
for(bound = n; bound > 0; --bound){
int flag = 0;
for(cur = 1; cur < bound; ++cur ){//cur - 1防越界
//flag: 标记一次冒泡过程是否发生元素交换
flag = 1;
if(a[cur] < a[cur - 1]){
Swap(&a[cur] , &a[cur - 1]);
}
}
//如果没有发生交换, 提前结束
if(flag == 0)
break;
}
}
六.快速排序(三种方法)
- hoare版本
- 挖坑法
- 前后指针版本
空间: 空间可以复用 最大的递归调用链–> logN
时间: N * logN
序列有序 时间效率最差–> N^2
三数取中对快排的三种方法进行优化
//三数取中法
int getMid(int* a, int left, int right){
int mid = left + (right - left) / 2;
if(a[mid] > a[left]){
if(a[mid] < a[right]){
return mid;
}else{
//mid >left, right
if(a[left] > a[right]){
return left;
}else{
return right;
}
}
}else{
//mid <= left
if(a[left] < a[right]){
return left;
}else{
//left >= right, mid
if(a[mid] > a[right])
return mid;
else
return right;
}
}
}
1.hoare版本
//快排的一次排序:确定基准值的位置
int PartSort(int* a, int left, int right){
int mid = getMid(a, left, right);
Swap(&a[mid], &a[left]);
int key = a[left];
int start = left;
//寻找大小元素交换
while(left < right){
//先从右边找小于key的值
while(left < right && a[right] >= key)
--right;
//从左边找大于key的值
while(left < right && a[left] <= key)
++left;
Swap(&a[left], &a[right]);
}
//key的位置确定 : left right 相遇的位置
Swap(&a[start], &a[left]);
return left;
}
2. 挖坑法
int Dig(int* a, int left, int right){
int mid = getMid(a, left, right);
Swap(&a[mid], &a[left]);
int key = a[left];
while (left < right){
//从右边找小
while (left > right && a[right] >= key){
--right;
}
a[left] = a[right];
//从左边找大的
while (left < right && a[right] <= key)
++left;
//填坑
a[right] = a[left];
}
//存放key
a[left] = key;
return left;
}
3.前后指针法
int PrevCurPointer(int* a, int left, int right){
int mid = getMid(a, left, right);
Swap(&a[mid], &a[left]);
//最后一个小于key的位置
int prev = left;
//下一个小于key的位置
int cur = left + 1;
int key = a[left];
while (cur <= right){
//如果下一个小于key的位置与上一个小于key的位置不连续
//说明中间有大于key的值, 进行交换, 大-->向后移动, 小<--向前移动
if(a[cur]< key && ++prev != cur){//不连续
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[prev], &a[left]);
return prev;
}
快速排序
void QuickSort(int* a, int left, int right){
if(left >= right)
return;
//小区间优化: 小区间不再调用递归
else if(right - left + 1 < 5){
InsertSort(a + left, right - left + 1);
}else{
int mid = PartSort(a, left, right);
QuickSort(a, left, mid - 1);
QuickSort(a, mid + 1, right);
}
}
快速排序非递归
void QuickSortNoR(int* a, int left, int right){//typedef int STDatatype
Stack st;
StackInit(&st);
if(left < right){
StackPush(&st, right);
StackPush(&st, left);
}
while (StackEmpty(&st) == 0){
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
//划分当前区间
int mid = PrevCurPointer(a, begin, end);
//划分左右子区间
if(begin < mid - 1){
StackPush(&st, mid - 1);
StackPush(&st, begin);
}
if(mid + 1 < end){
StackPush(&st, end);
StackPush(&st, mid + 1);
}
}
}
stack.h
#include <stdio.h>
#include <assert.h>
#include <malloc.h>
typedef int STDatatype;
typedef struct Stack{
STDatatype* _a; //数组指针
size_t _top;
size_t _capacity;
}Stack;
void StackInit(Stack* st);
void StackDestory(Stack* st);
void StackPush(Stack* st, STDatatype x);
void StackPop(Stack* st);
STDatatype StackTop(Stack* st);
int StackEmpty(Stack* st);
size_t StackSize(Stack* st);
七.归并排序
归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
时间: N * logN
空间: N + logN ~ N
稳定性: 稳定
void _MergeSort(int*a, int left, int right, int* tmp){
//区间只剩一个元素, 不需要分解和归并
if(left >= right)
return;
//分解
int mid = left + (right - left) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
//归并 [left, mid] , [mid + 1, right]
int begin1 = left, end1 = mid,
begin2 = mid + 1, end2 = right; //begin1 ~ end2 --> left ~ right
int tmpindex = begin1;
while (begin1 <= end1 && begin2 <= end2){
// < 不稳定 <= 稳定
if (a[begin1] <= a[begin2]){
tmp[tmpindex++] = a[begin1++];
}else{
tmp[tmpindex++] = a[begin2++];
}
}
while (begin1 <= end1){
tmp[tmpindex++] = a[begin1++];
}
while (begin2 <= end2){
tmp[tmpindex++] = a[begin2++];
}
//拷贝到原有数组的对应区间
memcpy(a + left, tmp + left, (right - left + 1) * sizeof(int));
}
void MergeSort(int* a, int n){
int* tmp = (int*)malloc(sizeof(int));
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
八.计数排序
计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
时间复杂度:O(MAX(N,范围))
空间复杂度:O(范围)
稳定性:稳定
void CountSort(int* a, int n){
//范围: min ~ max
int min = a[0], max = a[0];
int i;
//获取数据范围 可有可无 节省空间
for (i = 1; i < n; ++i){
if(a[i] < min)
min = a[i];
if(a[i] > max)
max = a[i];
}
int range = max - min + 1;
int* CountArr = (int*)malloc(sizeof(int) * range);
memset(CountArr, 0, sizeof(int) * range);
//计数
for (i = 0; i < n; ++i) {
CountArr[a[i] - min]++;
}
//排序
int index = 0;
for (i = 0; i < n; ++i) {
while(CountArr[i]--){
a[index] = i + min;
}
}
}
void PrintArray(int* a , int n){
int i;
for (i = 0; i < n ; ++i) {
printf("%d ", a[i]);
}
printf("\n");
}