数据结构 - 排序 | C (插入、希尔、选择、堆、冒泡)

0.排序

// Sort.h
#pragma once

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
#include "Stack.h"

// 插入排序
void InsertSort(int* a, int n);

// 希尔排序
void ShellSort(int* a, int n);

// 选择排序
void SelectSort(int* a, int n);


// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);

// 冒泡排序
void BubbleSort(int* a, int n);

思维导图(总)

在这里插入图片描述

一、插入排序

1.直接插入排序

思路分析
  • 升序排序

  • 一个数插入一个升序列,找到不比自己大的数,插入在这个数的后面
    在这里插入图片描述

  • 具体是如何插入的:

    • 如下图,如果 a[end]>tmp 就往后挪动数据→a[end+1]=a[end]当找到 不比tmp大的数 之后,将tmp插入到这个数的后面→a[end+1]=tmp
      在这里插入图片描述
  • 因此,对于一个无序列,就是依次插入数据
    在这里插入图片描述

代码实现
// 插入排序
void InsertSort(int* a, int n)
{
	assert(a);
	for (int i = 1; i < n; i++)
	{
		int tmp = a[i];
		int end = i - 1;
		while (end >= 0)
		{
			if (a[end] <= tmp)
			{
				break;
			}
			a[end + 1] = a[end--];
		}
		a[end + 1] = tmp;
	}
}
时间复杂度
  • 最坏的情况:排升序,但原数列为降序
    array[0]挪动数据并插入的次数为:0;
    array[1]挪动数据并插入的次数为:1;
    array[2]挪动数据并插入的次数为:2;
    array[3]挪动数据并插入的次数为:3;
    …………
    array[n]挪动数据并插入的次数为:n;
    Σ = 0+1+2+3+……+n = (1+n)*n/2

时间复杂度O(N)=N²

  • 最好的情况:时间复杂度O(N)=N

插入排序的适应性很强,对于有序、局部有序的情况,都能效率提升

2.希尔排序

思路分析

为了优化直接插入,我们选择进行预排序,让较大的位于前列的数先排到较后面的位置,以此减少挪动数据的次数
在这里插入图片描述
预排序:分组,每组进行插入排序

下图以gap==3为例
在这里插入图片描述
在这里插入图片描述
gap的变化:

while(gap)
{
	int gap = n / 2;
	gap /= 2;
}
//当gap==1时就是直接插入排序
//or
while(gap)
{
	int gap = n / 3;
	gap = (gap/3 + 1);
}
代码实现
// 希尔排序
void ShellSort(int* a, int n)
{
	assert(a);

	int gap = n / 2;
	while (gap)
	{
		for (int i = 1; i < n; i++)
		{
			int tmp = a[i];
			int end = i - gap;
			while (end >= 0)
			{
				if (a[end] <= tmp)
				{
					break;
				}
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = tmp;
		}
		gap /= 2;
	}
}
时间复杂度

希尔排序的时间复杂度算起来很复杂,预排序之后无法确定数列的顺序情况。
这里直接给结果:O(N)=N^1.3

二、选择排序

1.选择排序

思路分析

遍历数组(以排升序为例)
找到最一小的数与数组第一个位置的数交换;
找到第二小的数与数组第二个位置的数交换;
……(依次类推)
在这里插入图片描述

优化:两头缩进
从数组的头和尾开始,在中间(区间为👉[begin,end])同时找最大和最小的数,找到分别与“两头的数”交换,依次类推,“两头缩进”
在这里插入图片描述
找到后 Swap ↓

Swap(&a[begin], &a[mini]);
Swap(&a[end], &a[maxi]);
++begin;
--end;
  • 注意!:Swap之后:begin → min ; mini → original-a[begin]
    • 如果 original-a[begin] → max 则会影响 a[maxi] =? max
      if (begin == maxi)即如果原本 begin 指向的数是 max,则 Swap 之后这个数已经被交换到了 mini 所指向的位置
    • end 指向的数是否改变不影响,因为,我们只是要把 max 放在最后面的位置(同时保持数据不丢失)
    • 就是说 ,begin 代表头部的位置,end 代表尾部的位置;mini 是最小值的下标,maxi 是最大值的下标
      在这里插入图片描述
代码实现
//优化后:
void Swap(int* x, int* y)
{
	assert(x && y);
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

// 选择排序
void SelectSort(int* a, int n)
{
	assert(a);
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = end;

		//[begin+1,end-1]
		for (int j = begin; j <= end; j++)
		{
			if (a[j] < a[mini])
				mini = j;
			if (a[j] > a[maxi])
				maxi = j;
		}
		//swap之前 min==a[mini]  max==a[maxi]
		Swap(&a[begin], &a[mini]);
		//swap之后:begin → min ; mini → original-a[begin]
		//if(original-a[begin] → max)则会影响 a[maxi] =? max
		if (begin == maxi)//如果原本begin指向的数是max,则swap之后这个数已经被交换到了mini所指向的位置
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}
时间复杂度

未优化的算法:
从头找到尾
找到最一小的数与数组第一个位置的数交换,查找的次数为:n - 1;
找到第二小的数与数组第二个位置的数交换,查找的次数为:n - 2;
…………
最后一次查找次数为:1( n -(n-1))
Σ = 1+2+3+……+(n-1)= n*(n-1)/2

时间复杂度为O(N²)

2.堆排序

详细分析过程见→二叉树-堆| [堆的实现【建堆算法图解+分析】]

思路分析

排升序:建大堆。把数组按大堆的方式排列,此时堆顶就是最大的数,让堆顶与数组最后一个数交换,最大的数就被放在了数组的尾部。对于前面的部分,调整使其依旧为大堆,此时堆顶的数就是第二大的数……重复这个操作👉向下调整建堆,不断取堆顶的数
排降序:建小堆。思路同上。

代码实现
// 堆排序
void AdjustDwon(int* a, int n, int root)
{
	assert(a);
	int parent = root;
	int child = parent * 2 + 1;

	while (child < n)
	{
		if ((child + 1) < n && a[child + 1] > a[child])
			++child;
		if (a[parent] < a[child])
			Swap(&a[parent], &a[child]);
		else
			break;

		parent = child;
		child = parent * 2 + 1;
	}
}
void HeapSort(int* a, int n)
{
	assert(a);

	//建大堆
	for (int parent = (n - 1 - 1) / 2; parent >= 0; parent--)
	{
		AdjustDwon(a, n, parent);
	}

	//取堆顶的数据并向下调整
	int end = n - 1;
	while (end)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		end--;
	}
}
时间复杂度

向下调整建堆的时间复杂度:O(N)

取堆顶数据然后向下调整时间复杂度:O(logN) → 重复 N次 → O(N*logN)

时间复杂度 为 O(N*logN)

三、交换排序

冒泡排序

思路分析

前后比较交换

以下列这个数组为例,展示一趟冒泡排序:
在这里插入图片描述
在这里插入图片描述

代码实现
// 冒泡排序
void BubbleSort(int* a, int n)
{
	assert(a);
	for (int j = 0; j < n; j++)
	{
		for (int i = 0; i < n - j - 1; i++)
		{
			if (a[i] > a[i + 1])
				Swap(&a[i], &a[i + 1]);
		}
	}
}
时间复杂度

第一趟冒泡排序:n-1次
第二趟冒泡排序:n-2次
……
第n趟冒泡排序:n-n次
Σ = 1 + 2 + 3 + …… + n-1 = n(n-1)/2

时间复杂度为O(N)=N²


END

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

畋坪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值