数据结构——排序(1)

数据结构------排序(1)

文章目录

一、排序

1.概念:

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

2.运用:

购物筛选排序,院校排名等。

3.常见排序算法

二、实现排序算法

1.插入排序

1.1直接插入排序

基本思想:

待排序 的记录按其关键码值的大小 逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
实际中我们玩扑克牌时就用了插入排序的思想。

动图理解:

当插⼊第 i(i>=1) 个元素时,前⾯的 array[0],array[1],...,array[i-1] 已经排好序,此时用 array[i] 的排序码与 array[i-1],array[i-2],... 的排序码顺序进⾏⽐较,找到插⼊位置即将 array[i] 插⼊,原来位置上的元素顺序后移。

代码实现:

C 复制代码
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;//有序区间的最后一个数据
		int tmp = arr[end + 1];//有序区间的后一个数据
		
		while (end >= 0)
		{
			if (arr[end] > tmp)//当arr[end]>tmp,两值交换
			{
				arr[end + 1] = arr[end];//把tmp放在end的位置
				end--;  //end移动到有序区间的倒数第二个数据
			}
			else     //当arr[end]<=tmp,不做处理,结束循环
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}
  • 为什么for循环的判断条件是i<n-1,而不是i<n呢?

因为当end=n-1时,tmp=arr[n],这里越界了,所以for循环的判断条件是i<n-1。

  • 时间复杂度

最差的情况(数组有序且为降序 的情况下时间复杂度最差):时间复杂度为O(N^2^)

最好的情况(数组有序且为升序的情况下时间复杂度最差):时间复杂度为O(N)。

  • 空间复杂度:O(1)
1.2希尔排序

曾经我们学过冒泡排序,我们知道冒泡排序的算法效率极低(最差的时间复杂度为O(N^2^)),所以在实际工作中,我们不会用到它。即冒泡排序只有教学意义,没有实际意义。

直接插入排序有实际意义吗?

它有实际意义,但是要优化一下。当数组为降序序列时,直接插入排序还能得到优化吗?这里我们就要引出------希尔排序。

希尔排序法⼜称缩小增量法,是一种改进的插入排序算法。它通过比较相距一定间隔的元素来进行排序,逐步缩小间隔,最终间隔为1时,算法退化为普通的插入排序。希尔排序的名称来源于其发明者Donald Shell。

基本思想:

希尔排序法的基本思想是:先选定⼀个整数(通常是gap = n/3+1),把待排序⽂件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每⼀组内的记录进⾏排序,然后gap=gap/3+1得到下⼀个整数,再将数组分成各组,进⾏插⼊排序,当gap=1时,就相当于直接插⼊排序。

以排序数组为例(gap取2)

代码实现:

C 复制代码
void ShellSort(int* arr, int n)
{
	int gap = n;//6//gap表示我们要分多少组
	while (gap > 1)
	{
		gap = gap / 3 + 1;//6除3=2,2除以3=0 //+1是为了保证最后一次gap一定为1
		for (int i = 0; i < n - gap; i ++)
		{
			int end = i;
			int tmp = arr[end + gap];//tmp最后一个取值要保证不越界
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

时间复杂度分析:

外层循环:

外层循环的时间复杂度可以直接给出为: O (log2 n ) 或者 O (log3 n ) ,即 O (log n)

内层循环:
假设⼀共有n个数据,合计gap组,则每组为n/gap(大致)个;在每组中,插⼊移动的次数最坏的情况下为 S=1 + 2 + 3 + ......+ (n/gap-1),⼀共是gap组,因此:

总计最坏情况下移动总数为:gap ∗ S

gap取值有(以除3为例):n/3 n/9 n/27 ... 2 1

一 一带入:

  • 当gap为n/3时,移动总数为: n

  • 当gap为n/9时,移动总数为: 4n

  • 最后⼀趟,数组已经已基本有序了,gap=1即直接插入排序,移动次数就是n

通过以上的分析,可以画出这样的曲线图:


因此,希尔排序在最初和最后的排序的次数都为n,即前⼀阶段排序次数是逐渐上升的状态,当到达某⼀顶点时,排序次数逐渐下降⾄n,⽽该顶点的计算暂时⽆法给出具体的计算过程。

希尔排序时间复杂度不好计算,因为 gap 的取值很多,导致很难去计算,因此很多书中给出的希尔排序的时间复杂度都不固定。《数据结构(C语⾔版)》--- 严蔚敏书中给出的时间复杂度为:

总而言之,希尔排序的时间复杂度范围为:O(N^1.3^)~O(N^2^)

  • 希尔排序的时间性能优于直接插入排序的原因:

在希尔排序中,随着增量的减小,元素的移动次数会显著降低。当数据接近有序时,直接插入排序的性能显著提升,而希尔排序则更早地创建了部分有序的数据集合,使得后面的排序过程更加高效。

2.选择排序

2.1直接选择排序

基本思想:

每⼀次从待排序的数据元素中选出最⼩(或最⼤)的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

动图理解:

  1. 在元素集合 array[i]--array[n-1] 中选择关键码最⼤(⼩)的数据元素

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

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

代码实现:

C 复制代码
void SelectSort(int* arr,int n)
{
    int begin=0;
    int end=n-1;
    
    while(begin<end)
    {
        int mini=begin,maxi=begin;
        for(int i=begin+1;i<=end;i++)
        {
            if(arr[i]>arr[maxi])
            {
                maxi=i;
            }
            if(arr[i]<arr[mini])
            {
                mini=i;
            }
        }
       //避免maxi begin都在同一个位置,begin和mini交换之后,mini数据变成了最小的数据
        if(maxi==begin)
        {
            maxi=mini;
        }
        Swap(&arr[mini],&arr[begin]);
        Swap(&arr[maxi],&arr[end]);
        
        ++begin;
        --end;
    }
}
  • 时间复杂度是O(n^2^).
2.2堆排序

堆排序是指利⽤堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀种。它是通过堆来进行选择数据。需要注意的是排升序要建⼤堆,排降序建小堆。在上篇二叉树(下)中我们已经实现过堆排序,这⾥不再细述。

3.交换排序

3.1冒泡排序

冒泡排序是⼀种最基础的交换排序。之所以叫做冒泡排序,因为每⼀个元素都可以像小气泡⼀样,根据自身大小⼀点⼀点向数组的⼀侧移动。

动图理解:

代码实现:

C 复制代码
void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		int exchange = 0;
		for (int j = 0; j < n - i - 1; j++)
		{
			//升序
			if (arr[j] > arr[j + 1])
			{
				exchange = 1;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}
  • 时间复杂度(最差):O(N^2^).
3.2快速排序(采用二叉树递归的思想)

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法。

基本思想:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左⼦序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

C 复制代码
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//[left,right]--->找基准值mid
	int keyi = _QuickSort(arr, left, right);
	//左子序列:[left,ley-1]
	QuickSort(arr, left, keyi - 1);
	//右子序列:[keyi+1,right]
	QuickSort(arr, keyi + 1, right);
}
  • 空间复杂度:O(logn)
  • 时间复杂度:O(nlogn)

找基准值的三种方法

  • hoare版本

算法思路 :

1)创建左右指针,确定基准值;

2)从右向左找出⽐基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进⼊下次循环.

问题1:为什么跳出循环后right位置的值⼀定不⼤于key?

当 left > right 时,即right⾛到left的左侧,⽽left扫描过的数据均不⼤于key,因此right此时指向的数据⼀定不⼤于key

问题2:为什么left 和 right指定的数据和key值相等时也要交换?

相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据⼤量重复时,无法进⾏有效的分割排序。

C 复制代码
int _QuickSort1(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left<=right)//left和right相遇的位置的值比基准值要大
	{
		while (left <= right && arr[right] > arr[keyi])
		{
			right--;
		}
		//right找到比基准值小或等于
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;
		}
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		} 
	}
	
	Swap(&arr[keyi], &arr[right]);
	return right;
}
  • 挖坑法

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

C 复制代码
int _QuickSort2(int* arr, int left, int right)
{
	int hole = left;
	int key = arr[hole];

	while (left < right)
	{
		while (left<right && arr[right]>key)
		{
			--right;
		}
		arr[hole] = arr[right];
		hole = right;

		while (left < right && arr[left] < key)
		{
			++left;
		}
		arr[hole] = arr[left];
		hole = left;

	}
	arr[hole] = key;
	return hole;
}
  • lomuto前后指针法

    创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

C 复制代码
int _QuickSort3(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int keyi = left;

	while (cur<=right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);

	return prev;
}
相关推荐
呆呆的猫8 分钟前
【LeetCode】227、基本计算器 II
算法·leetcode·职场和发展
Tisfy10 分钟前
LeetCode 1705.吃苹果的最大数目:贪心(优先队列) - 清晰题解
算法·leetcode·优先队列·贪心·
余额不足1213830 分钟前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
yuanManGan2 小时前
数据结构漫游记:静态链表的实现(CPP)
数据结构·链表
火星机器人life3 小时前
基于ceres优化的3d激光雷达开源算法
算法·3d
虽千万人 吾往矣3 小时前
golang LeetCode 热题 100(动态规划)-更新中
算法·leetcode·动态规划
arnold664 小时前
华为OD E卷(100分)34-转盘寿司
算法·华为od
ZZTC4 小时前
Floyd算法及其扩展应用
算法
lshzdq4 小时前
【机器人】机械臂轨迹和转矩控制对比
人工智能·算法·机器人
2401_858286115 小时前
115.【C语言】数据结构之排序(希尔排序)
c语言·开发语言·数据结构·算法·排序算法