【数据结构】经典排序

这篇具有很好参考价值的文章主要介绍了【数据结构】经典排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 排序的概念和运用

1.1 概念

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

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

比较排序:通过比较两个元素的大小来确定元素在内存中的次序,我们常见的入选择排序、插入排序、比较排序与归并排序都属于比较排序。

非比较排序:通过确定每个元素之前,应该有多少个元素来排序,常见的非比较排序有基数排序、计数排序与桶排4序。

1.2 运用

排序在我们日常生活中运用十分常见,例如在京东买东西时,可以通过销量,评论数,新品,价格进行排序。

又比如世界五百强企业等,我们都可以运用排序。
【数据结构】经典排序
【数据结构】经典排序

2. 常规的排序算法介绍

【数据结构】经典排序

// 插入排序
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)
// 快速排序
void QuickSort(int* a, int left, int right);
// 归并排序
void MergeSort(int* a, int n)

一. 插入排序

1.1 直接插入排序

  • 基本思想:

直接插入排序是一种简单的插入排序法。

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

实际中我们玩扑克牌时,就用了插入排序的思想:
【数据结构】经典排序

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…

的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

【数据结构】经典排序

void InsertSort(int* a, int n)
	{
		assert(a);//断言,不为空指针
		for (int i = 0;i < n - 1;i++)
		{
			int end = i;
			int x = a[end + 1];//记录最后一个元素,为要插入的目标元素
			while (end >= 0)
			{
				//升序
				//如果目标元素大于tmp中元素,则往后移
				if (a[end] > x)
				{
					a[end + 1] = a[end];
					end--;
				}
				else
				{
					break;
				}
			}
			a[end + 1] = x;//将要插入的元素,插到不大于该数据的最后一个位置
		}
	}

直接插入排序的特性总结**:**

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

1.2 希尔排序

基本思想:

希尔排序法又称缩小增量法。

希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

【数据结构】经典排序

void ShellSort(int* a, int n)
	{
		int gap = n;
		while (gap > 1)
		{
			gap /= 2;//确保最后一次的gap==1,直接插入排序
			for (int i = 0;i < n - gap;i++)
			{
				int end = i;
				int x = a[end + gap];
				while (end >= 0)
				{
					if (a[end] > x)
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
					a[end + gap] = x;
				}
			}
		}
	}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的
    了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的
    对比。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3—
    N^2)。
  4. 稳定性:不稳定。

二. 选择排序

基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
【数据结构】经典排序

2.1 选择排序

基本思想:

在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素。

若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。

在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。

void SelectSort(int* a, int n)
	{
		int left = 0;
		int right = n - 1;//记录下标
		while (left < right)
		{
			int min = left;
			for (int i = left;i < right;i++)
			{
				if (a[i] < a[min])//比较,交换下标
				{
					min = i;//遍历找到最小值的下标,并记录
				}
			}
			swap(&a[left], &a[min]);//交换
			left++;
		}
	}

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

2.2 堆排序

基本思想:

  • 原则: 先将原数组建成堆,需要注意的是排升序要建大堆,排降序建小堆。 注:以大堆为例:树中所以父亲结点值都 >= 孩子,根是最大的。

  • 建堆: 一个根节点与子节点数据如果不符合大堆结构,那么则对根节点数据进行向下调整,而向下调整的前提是左右子树也符合大堆结构,所以从堆尾数据的根节点位置开始向下调整建大堆。
    注:堆的两个特性:
    结构性:用数组表示的完全二叉树。
    有序性:任一结点的关键字是其子树所以结点是最大结点(或最小结点)

  • 排序: 大堆堆顶数据一定是待排数据中最大的,将堆顶数据与堆尾数据交换,交换后将除堆尾数据看成新堆,对现堆顶数据进行向下调整成大堆,以此循环直至排列完毕。

  • 向下调整: 找到子节点中的较大数据节点比较,如果父节点数据比大子节点小则交换,直到不符合则停止向下交换,此时再次构成了一个大堆结构。

// 堆排序(升序)/建大堆
	void HeapSort(int* a, int n)
	{
		int i;
		//建大堆
		//最后一个结点的1父亲下标:[(n-1)-1]/2;
		for (i = (n - 1 - 1) / 2; i >= 0; i--)
		{
			Adjustdown(a, n, i);
		}
		for (i = n - 1; i >= 0; i--)
		{
			Swap(&a[0], &a[i]);//与当前堆尾数据交换
			Adjustdown(a, i, 0);//对交换后堆顶数据进行向下调整
		}
	}
	void Adjustdown(int* a, int n, int parent)
	{
		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]);
				//更新下标
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

三. 交换排序

基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。

特点:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

3.1 冒泡排序

基本思想:

每次遍历数组,对相邻的数据进行比较,大的元素往后放。

【数据结构】经典排序

void BubbleSort(int* a, int n)
	{
		for (int i = 0;i < n-1;i++)//遍历数组,n个元素只需要n-1趟,每次比较,都可以将最大值放末尾
		{
			for (int j = 0;j < n - i - 1;j++)//n-1-i:因为每遍历一趟,可以确定一个元素,所以n-1-i;
			{
				if (a[j] > a[j + 1])
				{
					swap(&a[j],&a[j+1])
				}
			}
		}
	}

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

3.2 快速排序

基本思想:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列。

左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值 然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

3.2.1 Hoare法

【数据结构】经典排序

int PartSort1(int* a, int left, int right)
{
 int keyi = left;  //取最左边的元素做key

 while (left < right)
 {
  //右先走,找小
  //left<right:避免left和right错过或者越界
  //=:避免死循环
  while (left < right && a[right] >= a[keyi])
  {
   right--;
  }

  //左后走,找大
  while (left < right && a[left] <= a[keyi])
  {
   left++;
  }
  Swap(&a[left], &a[right]);
 }
 //记录二者相遇的位置并让其与key交换
 int meet = left;
 Swap(&a[meet], &a[keyi]);
 return meet;//返回相遇时下标值
}

3.2.2 挖坑法

int PartSort2(int* a, int left, int right)
{
 int mid = GetMidIndex(a, left, right);
 Swap(&a[mid], &a[left]);//使得中间值永远在最左,便于决定谁先走
 int key = a[left];//保存key值
 int pivot = left;//保存坑下标
 while (left < right)
 {
  //右边先找
  while (left<right && a[right]>=key)
  {
   right--;
  }
  //填坑
  a[pivot] = a[right];
  pivot = right;
  //再从左边找
  while (left < right && a[left] <= key)
  {
   left++;
  }
  //填坑
  a[pivot] = a[left];
  pivot = left;
 }
 a[pivot] = key;
 return pivot;
}

3.2.3 前后指针/左右指针法

int PartSort3(int* a, int left, int right)
{
 int mid = GetMidIndex(a, left, right);
 Swap(&a[mid], &a[left]);
 //初始化前后指针
 int cur = left+1, prev = left;
 while (cur < right)
 {
  if(a[cur]<a[left] )//找到比基准值小的
  Swap(&a[++prev], &a[cur]);

  cur++;
 }
 Swap(&a[prev], &a[left]);//遍历结束将基准值放在定位点
 return prev;
}

3.2.4 分治法/递归法

在了解快排的思想之后,我们会发现快速排序的排序过程是这样的:

先选定一个 key 做基准值,经过单趟排序后 key 左边位置的元素都小于 key,右边位置的元素都大于 key,这就使得 key 的位置被最终确定,即我们一趟排序可以确定一个元素的位置。

现在我们只需要对 key 的左区间和右区间再进行单趟排序即可;key 的左区间经过单趟排序之后又会确定一个元素的位置,然后再对该元素的左右区间进行单趟排序,直到 key 的左右区间只有一个元素 (一个元素本身就有序,不需要再进行排序) 或者不存在左右区间时说明排序完成。

左右区间又可以被划分为左右区间,即不断被划分为子问题,这就是递归的思想。

// 快速排序递归实现
void QuickSort(int* a, int left, int right)
{
 //如果左右区间相等或者右区间小于左区间直接返回
 if (right <= left)
  return;
 //单趟排序 -- 确定单个元素的位置
 int keyi = PartSort1(a, left, right);
 //递归左区间
 QuickSort(a, left, keyi - 1);
 //递归右区间
 QuickSort(a, keyi + 1, right);
}

3.2.5 非递归法

void QuickSortNonR(int* a, int left, int right)
{
 //首先构建一个栈(C语言来说需要自己实现)
 ST st;
 StackInit(&st);
 StackPush(&st, left);//将左右区间入栈
 StackPush(&st, right);
 while (!StackEmpty(&st))
 {
  int end = StackTop(&st);//读取区间数据
  StackPop(&st);
  int begin = StackTop(&st);
  StackPop(&st);

  int mid = PartSort3(a, begin, end);//排序(排好基准值)
  //划分基准值的左右区间
  int begin1 = mid + 1, end1 = end;
  //先入右边区域(栈的特点是先入后出)
  if (end1 - begin1 + 1 > 1)
  {
   StackPush(&st, begin1);
   StackPush(&st, end1);
  }
  //再将左边区域入栈
  int begin2 = begin, end2 = mid-1;
  if (end2 - begin2 + 1 > 1)
  {
   StackPush(&st, begin2);
   StackPush(&st, end2);
  }
 }
 //到空栈则排序结束
 StackDestroy(&st);//栈销毁
}

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。
  2. 时间复杂度:O(N*logN)。
    1. 空间复杂度:O(logN)。
  3. 稳定性:不稳定。

四. 归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法 (Divide and
Conquer)的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

【数据结构】经典排序

void _MergeSort(int* a, int left, int right, int* tmp)
	{
		//当区间大小为1时,无左右区间,直接返回
		if (left >= right)
			return;

		int mid = left + (right - left) / 2;

		//递归左区间有序
		_MergeSort(a, left, mid, tmp);
		//递归右区间有序
		_MergeSort(a, mid + 1, right, tmp);

		//当左右区间都有序时开始归并 -- 取小的尾插
		int left1 = left, right1 = mid;  //左区间
		int left2 = mid + 1, right2 = right;  //右区间

		//在tmp数组中的相应位置尾插
		int i = left;
		while (left1 <= right1 && left2 <= right2)
		{
			if (a[left1] <= a[left2])
			{
				tmp[i++] = a[left1++];
			}
			else
			{
				tmp[i++] = a[left2++];
			}
		}

		//将较大的数组链接到tmp后面
		while (left1 <= right1)
		{
			tmp[i++] = a[left1++];
		}
		while (left2 <= right2)
		{
			tmp[i++] = a[left2++];
		}

		//将tmp中已归并的数据拷贝到原数组的相应区间上
		memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
	}

	// 归并排序递归实现
	void MergeSort(int* a, int n)
	{
		//开辟一个额外数组用于归并
		int* tmp = (int*)malloc(sizeof(int) * n);
		if (tmp == NULL)
		{
			perror("malloc fail\n");
			exit(-1);
		}

		//归并排序
		_MergeSort(a, 0, n - 1, tmp);

		//销毁
		free(tmp);
		tmp = NULL;
	}

归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

五.总结

【数据结构】经典排序

💓 感谢阅读!文章来源地址https://www.toymoban.com/news/detail-444844.html

到了这里,关于【数据结构】经典排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法】十大经典排序算法-插入排序

    🌟 个人博客: www.hellocode.top 🏰 Java知识导航: Java-Navigate 🔥 CSDN: HelloCode. 🌞 知乎 :HelloCode 🌴 掘金 :HelloCode ⚡如有问题,欢迎指正,一起学习~~ 插入排序(Insertion Sort)是一种简单直观的排序算法,其基本思想是将一个记录插入到已排好序的有序序列中,直到所有记录

    2024年02月13日
    浏览(83)
  • 【数据结构——9大基础排序】一文掌握九大经典排序(配有详细图文说明!!!)

    算法基本思想:(从大到小排序) 在一个 非递减 的有序数组中,要新增一个元素 x ,有两个方法: 1.从数组的头部开始向后遍历,寻找 第一个比x 大 的元素 y ,从y元素开始的所有元素全部向后移动,最后将x元素插入数组。(×) 2.从数组的尾部开始向后向前遍历,寻找 第

    2024年02月08日
    浏览(45)
  • 头歌数据结构实训参考---十大经典排序算法

    可通过 目录 快速查阅对应排序算法

    2024年02月04日
    浏览(64)
  • 追梦之旅【数据结构篇】——C语言手撕八大经典排序

        😎博客昵称:博客小梦 😊最喜欢的座右铭:全神贯注的上吧!!! 😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主! 😘博主小留言:哈喽! 😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不

    2024年02月17日
    浏览(48)
  • 数据结构(五)数据结构与算法中的经典题

    本文是在原本数据结构与算法闯关的基础上总结得来,加入了自己的理解和部分习题讲解。至此数据结构介绍已完结,后续会把数据结构算法题系列更完。 原活动链接 邀请码: JL57F5 根据要求完成题目 Q1. (单选)以下哪些数据结构支持随机访问? A. 数组 B. 单链表 C. 双向链表

    2024年01月20日
    浏览(44)
  • 【数据结构】二叉树经典题目

    相信大部分人看了题目描述之后,都会和我一样一脸的懵逼。直到我看到了一个描述才恍然大悟 分为3种情况: 左右都为空 --省略 右为空,左不为空 – 省略 左为空,右不为空–不省略 这里复习一下二叉树的前序遍历、中序遍历、和后序遍历 前序的结果是:ABDEGCF 中序的结

    2024年02月10日
    浏览(54)
  • 【数据结构】哈希经典应用:位图——[深度解析](8)

    前言 大家好吖,欢迎来到 YY 滴 数据结构 系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁 主要内容含: 欢迎订阅 YY 滴C++专栏!更多干货持续更新!以下是传送门! YY的《C++》专栏 YY的《C++11》专栏 YY的《Linux》专栏 YY的《数据结构》专栏 YY的《C语言基础》专栏 YY的《

    2024年02月03日
    浏览(42)
  • 【数据结构】栈与队列经典选择题

    🚀write in front🚀 📜所属专栏: 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!! 关注我,关注我,关注我 , 你们将会看到更多的优质内容!!   在前面的学习中外面学习了栈与队列。

    2023年04月23日
    浏览(47)
  • 【数据结构】栈与队列经典oj题

    🚀write in front🚀 📜所属专栏:初阶数据结构 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!! 关注我,关注我,关注我 , 你们将会看到更多的优质内容!!   栈两种线性表示都能实现

    2024年02月03日
    浏览(42)
  • 【Elacticsearch】 原理/数据结构/面试经典问题整理

    对Elacticsearch 原理/数据结构/面试经典问题整理的文章; 映射 | Elasticsearch: 权威指南 | Elastic 目录 Elacticsearch介绍 原理 建立索引原理 查询索引原理 更新索引原理 删除索引原理 分片副本机制,集群发现选举机制 ,负载机制,容错机制,扩容机制 数据类型 数据结构 先介绍倒排

    2024年02月10日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包