详细介绍:数据结构排序入门:核心排序(插入,冒泡,希尔,堆排序)(1)图文详解

1.排序的概念及分类

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

分类:插入排序(直接插入排序,希尔排序);选择排序(直接选择排序,堆排序);交换排序(冒泡排序,快速排序);归并排序

2.插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

思想:将end+1插入到[0,end]的有序数组中。

逻辑:先写单趟,将需要排序的一组数,下标分别为【0,end】,需要排序插入的值是位置在end+1的,将这个值记为tmp,tmp来跟数组中的值进行比较,当数组中的值小于tmp,就跟tmp进行交换,end要减减。如果相等或者大于,就停止,有可能end=-1了a[end+1]就是在a[0]的位置 将end+1位置的数据插入到【0,end】,保持有序, end的起始位置是0,0的话把1插入进去,end的最后一个位置是n-2,0~n-1是n个数字,要从最后往前走的数字下标是n-1,单趟变整体,for循环,i作为趟数

直接插入排序的特性:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)

代码展示:

//插入排序
void InsertSort(int* a, int n)
{
for (int i = 0; i = 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}

自测:

void TestInsertSort()
{
int a[] = { 2,4,1,5,7,3,9,8 };
InsertSort(a, 8);
PrintArray(a, sizeof(a) / sizeof(int));
}

3.冒泡排序

思想:一前一后两个数比较,大的往后交换

时间复杂度为O[N^2],最坏情况

最好情况O(N) 

优化情况:加一个flag,最开始置为0,交换完之后置为1,走出循环,如果flag等于0,break

//冒泡排序
void BubbleSort(int* a, int n)
{
for (int j = 0; j  a[i])
{
Swap(&a[i - 1], &a[i]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
}
}

4.希尔排序

如果插入排序排的是逆序的数组,那么它的效率 瞬间就下来了,这里有更好的方法改进这一不足:希尔排序。

思想:

把一组数据进行预排序,让数组接近有序,把数据分成gap组,假设是3组,间隔为3来一组,间隔是多少组分成多少组(画图),对每一组进行插入排序,也就是每间隔3的数据取出来进行排序,直到分完比较;

进行预排序之后再进行插入排序

void ShellSort(int* a, int n)
{
int gap = 3;
for (int j = 0; j = 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}

图解1:预排序(gap分组)

图解2:第一组排序

图解3:为什么i<n-gap? && j<gap?

gap有多大?

预排序,gap越大,大的可以越快跳到后面,小的数可以越快跳到前面,越不接近有序

gap越小,跳得越慢但是越接近有序,当gap等于1相当于插入排序就有序了

int gap=n; while(gap>1)循环结束条件为gap=1的时候插入排序

 gap=gap/3+1;+1就保证最后一次一定是1,(假如gap=2,gap/3+1=1),任何小于3的数字除以3都是1,任何大于3的数字除以3还在预排序gap>1时是预排序,gap==1的时候是插入排序

void ShellSort(int* a, int n)
{
assert(a);
int gap = n;
//不能写成大于0,因为gap的值始终>=1
while (gap > 1)
{
//只有gap最后为1,才能保证最后有序
//所以这里要加1
gap = gap / 3 + 1;
//这里只是把插入排序的1换成gap即可
//但是这里不是排序完一个分组,再去
//排序另一个分组,而是整体只过一遍
//这样每次对于每组数据只排一部分
//整个循环结束之后,所有组的数据排序完成
for (int i = 0; i = 0 && a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
}
}
void TestShellSort()
{
int a[] = { 3, 4, 6, 1, 2, 8, 3, 5, 7 };
ShellSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}

希尔排序的时间复杂度:

希尔排序O(N^1.3)

希尔排序的特性总结:

希尔排序是堆直接插入排序的优化,

当gap>1时都是预排序,目的是让数组更接近于有序,当gap==1时,数组已经接近有序的了,

这样就会很快,整体而言可以达到优化的效果;

希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的时间复杂度都不固定。

5.堆排序

/*
n: 数组大小
root: 根位置
*/
void AdjustDown(int* a, int n, int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child  a[child]) {
++child;
}
if (a[child] > a[parent]) {
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else{
break;
}
}
}
void HeapSort(int* a, int n)
{
assert(a);
// 建堆,先从最后两个叶子上的根(索引为(n - 2) / 2开始建堆
// 先建最小的堆,直到a[0](最大的堆)
// 这就相当于在已经建好的堆上面,新加入一个
// 根元素,然后向下调整,让整个完全二叉树
// 重新满足堆的性质
for (int i = (n - 2) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//end:表示最后一个位置
int end = n - 1;
//只剩一个数时,就不需要调整了
while (end > 0)
{
//0位置和最后一个位置交换
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
void TestHeapSort()
{
int a[] = { 3, 4, 6, 1, 2, 8, 0, 5, 7 };
HeapSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}

关于堆排序博主的https://blog.csdn.net/asciiletters/article/details/151291747?fromshare=blogdetail&sharetype=blogdetail&sharerId=151291747&sharerefer=PC&sharesource=asciiletters&sharefrom=from_link

有解读,欢迎各位指正!

posted @ 2025-09-17 19:23  yxysuanfa  阅读(5)  评论(0)    收藏  举报