数据结构——排序

2024-01-21

前言:哈喽小伙伴们好久不见,也是顺利的考完试迎来了寒假。众所周知, 不怕同学是学霸,就怕学霸放寒假 ,假期身为弯道超车的最佳时间,我们定然是不能懒散的度过。

今天我们就一起来学习数据结构初阶的终章—— 七大排序

本文所有的排序演示都为升序排序


目录

一.为什么要排序

二.七大排序

1.冒泡排序

2.选择排序

3.堆排序

4.插入排序

5.希尔排序

6.快速排序

7.归并排序

三.总结


一.为什么要排序

我们知道,数据结构的诞生是为了存放和管理众多的数据。而 排序 ,就是 最常用也是必不可少的数据管理方式 ,能够帮助我们更加轻松和方便的去找到自己想要的结果。


二.七大排序

数据结构中常用的排序有七种:

  • 冒泡排序
  • 选择排序
  • 堆排序
  • 插入排序
  • 希尔排序
  • 快速排序
  • 归并排序

下面我们就来一一讲解这七大排序的 排序方式及其性能优劣


1.冒泡排序

冒泡排序相信有很多小伙伴在学习C语言的时候就已经有所了解了,如下图所示,冒泡排序是 从一组数据的第一个元素开始 两个两个紧挨着进行比较,如果前者大于后者就进行交换 ,这样便可以使 每排序一次就可以将最大值置于数据末尾

注意,这里紧挨着排序是指, 1和2排完之后,2紧接着和3排

冒泡排序的原码如下:

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		bool flag = false;
		for (int i = 0; i < n - 1 - j; i++)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				flag = true;
			}
		}
		if (flag == false)
			break;
	}
}

能够看出,冒泡排序需要 两层循环 控制, 内层循环控制单趟排序,外层循环控制总排序次数

n表示数据个数 ,所以 总共需要排序n-1次 ,即 外层循环的次数,同样也是n个数据之间两两比较的次数

因为 每经过一轮排序,我们都能排好最大值,所以每下一次排序都比上一次少一个数据 ,所以我们通过 i < n - 1 - j 的方式就可以完美实现这一点。当然你也可以定义一个新变量去控制,但是这样会扩大空间复杂度。

冒泡排序的时间复杂度和空间复杂度:

  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)

在数据本来就有序的情况下,冒泡排序的最优时间复杂度为:O(N) ,即仅仅比较了数据大小,没有进行交换,所以我们可以设计一个 “探子”flag 去打探数据是否交换, 如果进行一次内循环发现没有交换,就说明数据本来就有序,便直接退出外循环,节省时间


2.选择排序

选择排序相较于冒泡排序,它是能够一次确定 头尾两个位置元素 的排序方式:

先定义begin和end两个标志点,分别代表要排序数据的头和尾,然后默认一段乱序数据的第一个元素同时为最小值min、最大值max ,然后 从第二个数据开始分别去和它们比较,更小的值替换min,更大的值替换max ,循环一次便能找到当前序列的最大值和最小值,然后 再去分别和头尾元素交换 ,如此循环便可得出有序序列。

原码如下:

void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

不难看出, 内层循环每进行一次,就能确定最大值和最小值 同时标志点begin和end向内移动,直到两者相遇,说明排序已经完成

值得注意的是,如果 恰好第一个数据就是最大值 ,那么 maxi将得不到最大值的下标 ,而此时我们 先交换了最小值和头元素,作为头元素的最大值的下标便变成了mini 所以要进行一次判断,如果最大值是头元素,就在交换了头元素和最小值之后,将mini赋值给maxi

选择排序的时间复杂度和空间复杂度:

  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)

3.堆排序

我们知道堆有 大堆和小堆 两个特性,而我们的堆排序则采用 大堆化小堆 来进行排序。

我们 默认一个堆按从上到下,从左到右的顺序为排序的顺序 ,这就要求我们要 将更大的数据往堆的右下角去移动 ,所以要 建小堆 ,但是单纯只是建个小堆,可能会 出现下一层的元素大于上一层的情况

所以我们要 先建大堆 ,这样 可以使根节点为为最大值,然后再将根节点与堆的最右下角元素交换 ,然后 再将剩下的元素继续调整为大堆 ,如此循环往复的去交换,便可实现排序。

源码如下:

//堆向下调整
void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] > 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)
{
	//建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	//排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

堆排序的时间复杂度和空间复杂度:

  • 时间复杂度O(N*logN)
  • 空间复杂度O(1)

4.插入排序

插入排序的原理是 从第二个数据开始 依次去和它前边的数据比较,如果前边的数据比它大,就进行交换,并继续向前比较,直到前边的数据比它小为止

源码入下:

void InsertSort(int* a, int n)
{
    for (int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = a[end + 1];
        while (end >= 0)
        {
            if (tmp < a[end])
            {
                a[end + 1] = a[end];
                --end;
            }
            else
                break;
        }
        a[end + 1] = tmp;
    }
}

插入排序的时间复杂度和空间复杂度:

  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)

5.希尔排序

希儿排序实际上是对插入排序的一种优化:先选定一个 整数n 把待排序数据分成个若干组,所有距离为n的数据在同一组内,并对每一组内的数据进行排序 。然后, 取更小的整数n重复上述分组和排序的工作 。直到当 n == 1 时, 所有数据在同一组内进行一次插入排序

源码如下:

void ShellSort(int* a, int n)
{
	int gap = n;
	//gap > 1是预排序,目的是让数据接近有序
	//gap = 1是直接插入排序,目的是让数据有序
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
					break;
			}
			a[end + gap] = tmp;
		}
	}
}

gap即为所需要的整数 ,一般情况下, 先让他等于数据长度,然后每次排序时都除以3再+1 。这个并不固定,只要能保证最后gap要为1即可。

希尔排序的时间复杂度和空间复杂度:

  • 时间复杂度:平均为O(N^1.3)
  • 空间复杂度:平均为O(1)

6.快速排序

先来看动图:

默认第一个元素为key位 ,然后 头尾两个“先锋兵”开始行动,其中R先向左移动,去找到比key小的值并停下,然后L开始向右移动,找到比key大的值并停下,然后交换L和R所在位置的值,并让R开始继续移动,重复上述操作 直到R和L相遇,交换相遇点与key的值,并让相遇点成为新的key

如此操作之后,我们不难看出 最后key的左边的值都比key小,而右边的值都比key大 。这样我们 就唯一确定了key的位置。随后我们就需要去分别排序key的左半段和右半段了 ,很显然,这需要用到 递归

源码如下:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	int key = begin;
	while (left < right)
	{
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

快速排序的时间复杂度和空间复杂度:

  • 时间复杂度O(N*logN)
  • 空间复杂度O(N*logN~O(N))

7.归并排序

先看动图:

不难看出,归并排序也是 类似于一组一组的排完序后再整体进行排序

假设我们将数据不断拆分为4组,每个小组内部先进行排序,而后1,2组排序,3,4组排序,最后排完序的两组在整体排序

很显然, 这样的排序方式同样需要用到递归,而且用一个数组是无法完成的,所以我们需要新建一个数组进行排序,再将排好的数据拷贝回去

源码如下:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;

	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort->malloc");
		exit(-1);
	}
	
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

定义mid将数据拆分为两组 进行两组之间的排序,将排好的顺序记录在新数组tmp中 ,最后再 利用memcpy拷贝回数组a中

值得注意的是,在每个子递归函数中, 所递归的数据的头都是begin,尾是end ,所以 拷贝时要注意地址以及长度的写法

归并排序的时间复杂度和空间复杂度:

  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)

三.总结

七大排序的基础知识到这里就介绍完啦,而 数据结构初阶的知识到这里也全部落下帷幕啦

喜欢博主文章的小伙伴记得 一键三连 哦!

点关注不迷路,博主带你玩转编程!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构——排序 的相关文章

  • 【计算机毕业设计】springbootstone音乐播放器的设计与实现

    随着我国经济的高速发展与人们生活水平的日益提高 人们对生活质量的追求也多种多样 尤其在人们生活节奏不断加快的当下 人们更趋向于足不出户解决生活上的问题 stone音乐播放器展现了其蓬勃生命力和广阔的前景 与此同时 为解决用户需求 stone
  • RubyMine for Mac/win:提升Ruby和Rails开发的强大IDE

    随着Ruby和Rails在Web开发领域的广泛应用 一款高效的开发工具对于提高生产力至关重要 JetBrains RubyMine正是这样一款值得信赖的集成开发环境 IDE 作为Mac和Windows平台上的强大工具 RubyMine为开发

随机推荐

  • U3D游戏开发中摇杆的制作(UGUI版)

    在PC端模拟摇杆 实现玩家通过控制摇杆让玩家移动 以下是完整代码 using System Collections using System Collections Generic using UnityEngine using Unity
  • U3D游戏开发中摇杆的制作(NGUI版)

    在PC端模拟摇杆 实现控制摇杆让玩家或者物体移动 以下是完整代码 using System Collections using System Collections Generic using UnityEngine public clas
  • 游戏开发常见操作梳理之NPC任务系统

    多数游戏存在任务系统 接下来介绍通过NPC触发任务的游戏制作代码 using System Collections using System Collections Generic using UnityEngine
  • 游戏开发创建操作之玩家信息系统的建立

    游戏一般都需要玩家信息系统 那么我们应该如何搭建玩家信息系统 接下来我将展示一种简单的方法 完整代码如下 using System Collections using System Collections Generic using Uni
  • iStat Menus:Mac用户的系统状态守护者

    iStat Menus 一款为Mac用户精心设计的系统状态监控工具 致力于为用户提供关于系统性能 硬件状态和网络活动的实时信息 它不仅是一个监控工具 更是一个守护者 始终守护着Mac系统的安全与稳定 通过直观的仪表盘风格 iStat Men
  • 游戏开发常见操作梳理系列之——玩家信息的显示系统

    在游戏中 有不少游戏在左上角会出现玩家的头像和等级以及血量 这就是玩家的信息显示系统 那么这些是如何制作的呢 接下来我将讲讲代码的操作 其它操作我会在其它笔记中一一说明 敬请期待 信息的显示相当简单就是控制一些UI 然后在其它系统里面填写相
  • 提升编程效率,Sublime Text 4 for Mac 让代码编辑更高效!

    作为一名开发人员或程序员 一个高效且功能强大的文本编辑器是必不可少的工具 而 Sublime Text 4 for Mac 正是为满足这一需求而设计的 无论你是初学者还是经验丰富的专业人士 Sublime Text 4 都将成为你编程生涯中
  • 游戏开发常见操作梳理之NPC药品商店系统(NGUI版)

    后续会出UGUI Json的版本 敬请期待 游戏开发中经常会出现药品商店 实际操作与武器商店类似 甚至根据实际情况可以简化设置 废话不多说 直接上代码 药品商店的源码 using System Collections using Syste
  • 揭秘网络世界的幕后密码——Wireshark网络协议分析软件

    在我们日常生活中 计算机和互联网已经成为不可或缺的一部分 然而 很少有人真正了解网络背后复杂的工作原理和通信协议 幸运的是 有一款强大而实用的软件 Wireshark 可以帮助我们深入了解网络世界的幕后密码 Wireshark是一款免费的网
  • 解锁思维潜能,畅享XMind 2024 Mac/win中文版思维导图软件

    XMind 2024是一款功能强大的思维导图软件 旨在帮助用户提高工作效率和组织思维 它的核心特点包括多平台同步 强大的协作功能和丰富的导图模板 首先 XMind 2024支持多平台的无缝同步 用户可以在电脑 手机和平板上随时随地访问和编辑
  • 游戏开发常见操作梳理之小地图的制作

    游戏中一般存在小地图系统 实际上就是设置一个新的摄像机放置在玩家的正上方 然后在小地图上显示新摄像机看见的东西就可以了 在小地图上一般存在放大地图和缩小地图的按钮可以方便放大和缩小地图 这些操作是如何实现的呢 接下来直接上核心代码 usin
  • 用Growly Draw for Mac,释放您的创意绘画天赋!

    在数字化时代 绘画已经不再局限于传统的纸笔之中 如今 我们可以借助强大的绘画应用软件 将创意化为独特的艺术作品 而Growly Draw for Mac就是一款让您能够快速释放创意 创作精美绘画作品的应用软件 Growly Draw for
  • 游戏开发之常见操作梳理——武器装备商店系统(NGUI版)

    游戏开发中经常出现武器商店 接下来为你们带来武器装备商店系统的具体解决办法 后续出UGUI Json版本 敬请期待 武器道具的具体逻辑 using System Collections using System Collections Ge
  • 游戏开发常见操作梳理之角色选择一

    进入游戏后 我们经常会进入角色选择的界面 通常是左右两个按钮可以更改角色供玩家选择 对于这种界面我们通常使用数据持久化将角色信息存储起来 接下来的笔记中 我将使用自带的数据持久化系统对其进行操作 实现角色的选择页面 后续会更新xml系列的文
  • Effective C++——尽可能使用const

    const允许指定一个语义约束 也就是指定一个 不该被改动 的对象 而编译器会强制实施这项约束 只要保持某个值不变是事实 就应该说出来 以获得编译器的协助 保证不被违反 const与指针 注意const的写法 const char p p可
  • 游戏开发常用实践操作之按动任意键触发

    接下来一些笔记会对于一些大大小小的实践操作进行记录 希望对你有所帮助 在游戏中 我们经常会遇到一些按动任意键触发的操作 接下来展示核心代码 以下是对于Unity中的操作 使用的UI是NGUI 对于核心操作没有影响 你可以自己置换 void
  • 游戏开发常见操作系列之敌人系统的开发一(U3D)

    在开发游戏的过程中 我们常常会出现一些敌人攻击我们玩家 并且实现掉血以及死亡的现象 敌人还会源源不断地生成 这是怎么制作的呢 接下来为大家提供方法 其中使用了NGUI 后续会更新其它方法 敬请期待 使用HUDText实现扣血时显示文本 直接
  • 【C++】static_cast和dynamic_cast使用详解

    目录 一 static cast 二 dynamic cast 三 总结 如果这篇文章对你有所帮助 渴望获得你的一个点赞 一 static cast static cast 是 C 中的一种 类型转换操作符 用于执行编译时的类型转换 它主要
  • 探索Web开发的未来——使用KendoReact服务器组件

    Kendo UI 是带有jQuery Angular React和Vue库的JavaScript UI组件的最终集合 无论选择哪种JavaScript框架 都可以快速构建高性能响应式Web应用程序 通过可自定义的UI组件 Kendo UI可
  • 数据结构——排序

    前言 哈喽小伙伴们好久不见 也是顺利的考完试迎来了寒假 众所周知 不怕同学是学霸 就怕学霸放寒假 假期身为弯道超车的最佳时间 我们定然是不能懒散的度过 今天我们就一起来学习数据结构初阶的终章 七大排序 本文所有的排序演示都为升序排序 目录