2026.5.17数据结构 八大排序

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <assert.h>

#include <memory.h>

/*

八大排序

难易程度:

4个简单:直接插入排序

4个难:希尔排序

(代码实现话,如果是简单排序,则代码就是双重for循环,里面套一个if)(除了基数)

稳定性:

4个稳定:直接插入排序

4个不稳定:希尔排序

*/

//时间复杂度O(n^2) 空间复杂度O(1) 稳定性:稳定

void Insert_Sort(int arr[], int len)

{

for (int i = 1; i < len; i++)//控制的是趟数

{

int tmp = arr[i];

int j = i - 1;

for (; j >= 0; j--)//控制的是对已排序好的序列的从右至左的访问

{

if (arr[j] > tmp)//如果比我取出来的值tmp,向后挪动

{

arr[j+1] = arr[j];

}

else

{

break;//情况1:遇到了不大于我的值,停下来,把值放回去

}

}

//情况2:越界了,停下来,把值放回去

arr[j+1] = tmp;

}

}

void Show(int arr[], int len)

{

for (int i = 0; i < len; i++)

printf("%d ", arr[i]);

printf("\n");

}

//这个函数代表希尔排序中的单趟处理

void Shell(int arr[], int len, int gap_val)

{

//此时增量为5

//需要此时将每一个组执行直接插入排序

for (int i = gap_val; i < len; i++)//此时i控制的需要往前插入的待排序值

{

int tmp = arr[i];

int j = i - gap_val;

for (; j >= 0; j-=gap_val)//控制的是对已排序好的序列的从右至左的访问

{

if (arr[j] > tmp)//如果比我取出来的值tmp,向后挪动

{

arr[j + gap_val] = arr[j];

}

else

{

break;//情况1:遇到了不大于我的值,停下来,把值放回去

}

}

//情况2:越界了,停下来,把值放回去

arr[j + gap_val] = tmp;

}

}

//希尔排序时间复杂度取决于缩小增量的赋值 O(n^1.3 ~ n^1.7) 或者O(n^1.5) 空间复杂度O(1) 稳定性:不稳定

void Shell_Sort(int arr[], int len)

{

int Gap[] = { 5,3,1 };

int len_Gap = sizeof(Gap) / sizeof(Gap[0]);

for (int i = 0; i < len_Gap; i++)//趟数

{

Shell(arr, len, Gap[i]);

}

}

//选择排序 时间复杂度O(n^2) 空间复杂度O(1) 稳定:不稳定

void Select_Sort(int arr[], int len)

{

for (int i = 0; i < len - 1; i++)//控制的是趟数

{

int min = i;

for (int j = i + 1; j < len; j++)//控制的是这一趟中对于待排序序列值的遍历,从而找到待排序序列最小值所在位置

{

if (arr[j] < arr[min])//如果发现更新的值,则更新min指向

{

min = j;

}

}

if (min != i)//判断待排序序列最小值和其第一个值是否是同一个值

{

int tmp = arr[min];

arr[min] = arr[i];

arr[i] = tmp;

}

}

}

//时间复杂度O(n^2) 空间复杂度O(1) 稳定性:稳定

void Bubble_Sort(int arr[], int len)

{

for (int i = 0; i < len - 1; i++)//控制的是趟数

{

//[j, j+1] //j是两两比较中偏左的那个值 j+1是两两比较中偏右的那个值

for (int j = 0; j + 1 < len - i; j++)//控制的是这一趟中对于待排序序列的从左向右的两两取值比较的过程

{

if (arr[j] > arr[j + 1])//两两比较时,如果左边值>右边值,需要交换

{

int tmp = arr[j];

arr[j] = arr[j + 1];

arr[j + 1] = tmp;

}

}

}

}

//冒泡的优化 时间复杂度最好是O(n)

void Bubble_Sort2(int arr[], int len)

{

for (int i = 0; i < len - 1; i++)//控制的是趟数

{

bool tag = true;

//[j, j+1] //j是两两比较中偏左的那个值 j+1是两两比较中偏右的那个值

for (int j = 0; j + 1 < len - i; j++)//控制的是这一趟中对于待排序序列的从左向右的两两取值比较的过程

{

if (arr[j] > arr[j + 1])//两两比较时,如果左边值>右边值,需要交换

{

int tmp = arr[j];

arr[j] = arr[j + 1];

arr[j + 1] = tmp;

tag = false;

}

}

if (tag)

{

return;

}

}

}

//归并排序 两个有序组合并成一个有序组的函数

void Merge(int arr[], int left, int mid, int right)

{

//[left, mid] [mid+1, right]

int* brr = (int *)malloc(right - left + 1);

if (NULL == brr)

exit(EXIT_FAILURE);

int i = left;

int j = mid + 1;

int k = 0;//指向brr中接收值的格子下标

while (i <= mid && j <= right)

{

if (arr[i] <= arr[j])

brr[k++] = arr[i++];

else

brr[k++] = arr[j++];

}

while (i <= mid)//假设i指向的左边组没空

brr[k++] = arr[i++];

while (j <= right)//假设j指向的右边组没空

brr[k++] = arr[j++];

for (int w = left; w <= right; w++)

arr[w] = brr[w-left];

}

//归并排序 递归分函数实现

void Divide(int arr[], int left, int right)

{

if (left >= right)

{

return; //[3 13]

}

int mid = (left + right) / 2;

Divide(arr, left, mid);//分好的左边组的范围 [left, mid]

Divide(arr, mid + 1, right);//分好的右边组的范围 [mid+1, right]

Merge(arr, left, mid, right);

}

//归并排序 递归实现方法

void Merge_Sort(int arr[], int len)

{

//执行分 递归函数

//divide merge

Divide(arr, 0, len - 1);

}

void Heap_Adjust(int arr[], int start, int end)

{

//1.先讲根节点的值,取出,放到tmp里

int tmp = arr[start];

//2.再申请一个变量maxchild,用来指向当前空白格子的较大值(默认指向其左孩子)

int maxchild = start * 2 + 1;

//3.进入while循环,循环条件是"当前"的空白格子是否有有孩子(本质上是用maxchild是否合法来判断)

while(maxchild <= end)

{

//4.因为此时maxchild默认保存的空白格子的左孩子,这里进一步判断其右兄弟是否存在,且值更大

if (maxchild + 1 <= end && arr[maxchild + 1] > arr[maxchild])

{

maxchild++;

}

//5.此时maxchild验证之后,一定是指向较大孩子了,此时再和tmp的孩子进行比较

//6.可能性1:这个maxchild指向的较大的孩子的值 <= tmp的值 则此时可以将tmp挪动回去,结束

if (arr[maxchild] <= tmp)

{

arr[start] = tmp;

return;

}

//7.可能性2:这个maxchild指向的较大的孩子的值 > tmp的值 则将较大孩子的值,想上挪动到空白

// 格子位置,此时肯定出现新的空白格子,则让start和maxchild更新一下,重新进入循环

else

{

arr[start] = arr[maxchild];

start = maxchild;

maxchild = start * 2 + 1;

}

}

//8.如果while循环进不来,就说明此时的空白格子的左孩子下标非法(换句话说:空白格子触底了),则

// 将tmp挪动回去,结束

arr[start] = tmp;

return;

}

void Heap_Adjust2(int arr[], int start, int end)

{

//1.先讲根节点的值,取出,放到tmp里

int tmp = arr[start];

//2.再申请一个变量maxchild,用来指向当前空白格子的较大值(默认指向其左孩子)

int maxchild = 0;

int leftchild = 2 * start + 1;

//3.进入while循环,循环条件是"当前"的空白格子是否有有孩子(本质上是用maxchild是否合法来判断)

while (leftchild <= end)

{

maxchild = leftchild;

//4.因为此时maxchild默认保存的空白格子的左孩子,这里进一步判断其右兄弟是否存在,且值更大

if (leftchild + 1 <= end && arr[leftchild + 1] > arr[leftchild])

{

maxchild = leftchild + 1;//maxchild++;

}

//5.此时maxchild验证之后,一定是指向较大孩子了,此时再和tmp的孩子进行比较

//6.可能性1:这个maxchild指向的较大的孩子的值 <= tmp的值 则此时可以将tmp挪动回去,结束

if (arr[maxchild] <= tmp)

{

arr[start] = tmp;

return;

}

//7.可能性2:这个maxchild指向的较大的孩子的值 > tmp的值 则将较大孩子的值,想上挪动到空白

// 格子位置,此时肯定出现新的空白格子,则让start和maxchild更新一下,重新进入循环

else

{

arr[start] = arr[maxchild];

start = maxchild;

leftchild = start * 2 + 1;

}

}

//8.如果while循环进不来,就说明此时的空白格子的左孩子下标非法(换句话说:空白格子触底了),则

// 将tmp挪动回去,结束

arr[start] = tmp;

return;

}

//堆排序 时间复杂度O(nlogn) 空间复杂度O(1) 不稳定

void Heap_Sort(int arr[], int len)

{

//1.将此时数组映射的完全二叉树,由内到外的调整一遍,调整为大顶堆

// 由内到外的调整一遍(指的是从其最后一个非叶子节点作为根节点的子

// 树开始调整,由右至左,由下至上)

for (int i = (len - 1 - 1) / 2; i >= 0; i--)

Heap_Adjust2(arr, i, len-1);

//4.反复执行23,直到树中只剩下一个节点为止

for (int i = len - 1; i >= 1; i--)//i代表的是要头尾交换的尾结点下标

{

//2.头尾交换,然后断开尾结点链接(大顶堆的特点就是其根节点是整体的

// 最大值)

int tmp = arr[0];//头尾交换:头0下标 尾i下标

arr[0] = arr[i];

arr[i] = tmp;

//3.此时头尾交换完成之后,大顶堆性质被破坏,需要再调整为大顶堆(这

// 里只需要调整一次最外层框框即可)

Heap_Adjust2(arr, 0, i - 1);

}

}

//12345 -> 5

int Get_MaxNum_Figure(int arr[], int len)

{

int max = arr[0];

for (int i = 1; i < len; i++)

{

if (arr[i] > max)

max = arr[i];

}

if (max == 0)

return 1;

int num = 0;

while (max != 0)

{

max /= 10;

num++;

}

return num;

}

//3.获取当前值arr[i],在digit这一个位上,值是多少(插入到哪一个队列中)

//12345, 3 => 2

//12345, 1 => 4

//12345,5 => 0

int Get_Num_Digit(int num, int digit)

{

for (int i = 0; i < digit; i++)

num /= 10;

return num % 10;

}

#include <queue>

//单独处理一遍, //digit=0代表按个数处理 digit=1代表按十位处理

void Radix(int arr[], int len, int digit)

{

//1.申请10个队列

std::queue<int> Buckets[10];

//2.进入for循环,依次访问待排序序列全部元素

for (int i = 0; i < len; i++)

{

//3.获取当前值arr[i],在digit这一个位上,值是多少(插入到哪一个队列中)

int index = Get_Num_Digit(arr[i], digit);

//4.将该值插入到对应的队列中

Buckets[index].push(arr[i]);

}

//5.此时待排序序列所以的数据,都已经插入到对应的队列中了,最后只需要将0->9号队列值依次取出即可

int k = 0;

for (int i = 0; i <= 9; i++)//i代表队列号

{

//6.将此时i号队列中的值,全部取出放到arr[i]中

while (!Buckets[i].empty())

{

arr[k++] = Buckets[i].front();

Buckets[i].pop();

}

}

}

//基数排序 时间复杂度O(d(n+r)) 空间复杂度O(n+r) 稳定

void Radix_Sort(int arr[], int len)

{

//1.先获取待排序序列中最大值的位数

int fig = Get_MaxNum_Figure(arr, len);

//2.进入for循环,只要i还合法

for (int i = 0; i < fig; i++)//i=0代表按个数处理 i=1代表按十位处理

{

Radix(arr, len, i);

}

}

//单次划分,用的方法是挖空法 (Partition)

int Partition(int arr[], int left, int right)

{

//1.先讲最左端的值,取出用tmp保存,防止一会被覆盖

int tmp = arr[left];

//2.进入while循环,条件是left没有和right相遇

while (left < right)

{

//3.先从右向左找,找一个比基准值小于或者等于的值,找到后扔到左边空位上

while (left<right && arr[right] > tmp)

right--;

//这个内部while循环结束,只会有两种可能性:

//可能1:随着right--,两个指针相遇了

//可能2:两个指针还没有相遇,但是确实找到了一个小于等于tmp的值,由此时的right指向

if (left == right)

{

break;

}

arr[left] = arr[right];

//4.再从左向右找,找一个比基准值大于的值,找到后扔到右边空位上

while (left<right && arr[left] <= tmp)

left++;

if (left == right)

{

break;

}

arr[right] = arr[left];

}

//5.当while循环结束,表示left和right相遇了,此时就可以将tmp保存的基准值放回去了

arr[left] = tmp;//arr[right]=tmp;

return left; //return right

}

//时间复杂度O(nlogn) 空间复杂度O(logn) 稳定性:不稳定

void Quick(int arr[], int left, int right)

{

//挖空法 (Partition)

//left = right 只有一个值

//left < right 不止一个值(>=2)

//left > right 一个值都没有

if (left >= right)

{

return;

}

int par = Partition(arr, left, right);

Quick(arr, left, par-1);

Quick(arr, par+1, right);

}

//快速排序

void Quick_Sort(int arr[], int len) //递归的方式

{

Quick(arr, 0, len - 1);

}

#include <stack>

//快速排序

void Quick_Sort_No_Recursion(int arr[], int len) //非递归的方式

{

std::stack<int> st;//因为是左右边界 所以入栈一次两个值 出栈也一次两个值

st.push(0);

st.push(len - 1);

while (!st.empty())

{

int right = st.top();

st.pop();

int left = st.top();

st.pop();

int par = Partition(arr, left, right);//[left, par-1] par [par+1, right]

if (left < par - 1)//[left, par-1]

{

st.push(left);

st.push(par - 1);

}

if (par + 1 < right)//[par+1, right]

{

st.push(par + 1);

st.push(right);

}

}

}

int main()

{

int arr[] = { -122,-33,-455,-66,109,75,20,91,4,75,90,170,15,92,7,5,40,91,7906,15,7,209,56,71,90,54 };

int len = sizeof(arr) / sizeof(arr[0]);

printf("排序之前:\n");

Show(arr, len);

//Insert_Sort(arr, len);

//Shell_Sort(arr, len);

//Select_Sort(arr, len);

//Bubble_Sort(arr, len);

//Bubble_Sort2(arr, len);

//Merge_Sort(arr, len);

//Heap_Sort(arr, len);

//Radix_Sort(arr, len);

//Quick_Sort(arr, len);

Quick_Sort_No_Recursion(arr, len);

printf("排序之后:\n");

Show(arr, len);

return 0;

}

相关推荐
毋语天4 小时前
NumPy 完全入门指南:核心数据结构与高效数值计算
数据结构·numpy
加号34 小时前
【C#】 实现 CRC16 校验:原理、算法与工程实践
算法·c#
khalil10204 小时前
代码随想录算法训练营Day-55 图论06 | 108.冗余连接、109.冗余连接II
c++·算法·leetcode·图论·并查集
进击的荆棘4 小时前
优选算法——字符串
开发语言·c++·算法·leetcode·字符串
夏日听雨眠4 小时前
排序(直接插入排序,希尔排序)
数据结构·算法·排序算法
Kiling_07045 小时前
Java Map集合详解与实战
java·开发语言·python·算法
WL_Aurora5 小时前
备战蓝桥杯国赛【Day 18】
python·算法·蓝桥杯
拽着尾巴的鱼儿5 小时前
国密算法 Spring Boot 实战:SM2/SM3/SM4 完整集成指南
spring boot·后端·算法
Hesionberger5 小时前
LeetCode105:前序中序构建二叉树(三解法)
java·数据结构·python·算法·leetcode·深度优先