ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

《大话数据结构》第九章:排序(笔记)

2021-03-04 22:00:44  阅读:128  来源: 互联网

标签:排序 记录 第九章 大话 int length 序列 数据结构 复杂度


第九章:排序

排序:假设含有n个记录的序列为:
{ r 1 , r 2 , . . . . , r n } \lbrace r_1,r_2,....,r_n \rbrace {r1​,r2​,....,rn​}
其相应的关键字分别为:
{ k 1 , k 2 , . . . . . . , k n } \lbrace k_1,k_2,......,k_n \rbrace {k1​,k2​,......,kn​}
需要确定1,2…,n的一种排列P1,P2…,Pn,使其相应的关键字满足:
K p 1 ≤ K p 2 ≤ . . . . . . ≤ K p n K_{p1}≤K_{p2}≤......≤K_{pn} Kp1​≤Kp2​≤......≤Kpn​
(非递减或非递增)关系,即使得序列成为一个按照关键字有序:
{ r p 1 , r p 2 . . . . . . , r p n } \lbrace r_{p1},r_{p2}......,r_{pn}\rbrace {rp1​,rp2​......,rpn​}
这样的操作就称为排序。

排序的稳定性

假设Ki=Kj(1≤i≤n,1≤j≤n,i≠j),且在排序前的序列中ri领先于rj(即i<j)。如果排序后ri仍领先于rj,则称所用的排序方法是稳定的;反之,若可能使排序后的序列中rj领先于ri,则称所用的排序方法是不稳定的。

排序的分类

内排序与外排序: 根据在排序过程的记录中是否全部被放置在内存中,排序分为:内排序和外排序

对于内排序,影响性能的因素:

  • 时间性能,排序算法的时间开销是衡量其好坏的重要的标志。
  • 辅助空间,执行算法所需要的辅助存储空间。
  • 算法的复杂性,算法本身的复杂性而不是指算法的时间复杂度。

内排序: 插入排序,交换排序,选择排序和归并排序

简单算法与改进算法: 按照算法的复杂度分为简单算法和改进算法,本章共介绍7中算法

简单算法:

  • 冒泡排序、简单选择排序、直接插入排序

改进算法:

  • 希尔排序、堆排序、归并排序、快速排序

冒泡排序

冒泡排序(Bubble Sort): 一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

核心代码:

#include <math.h>
#include <iostream>
using namespace std;
#define MAXSIZE 10

typedef struct
{
	int r[MAXSIZE + 1];
	int length;
}SqList;

void swap(SqList* L, int i, int j)
{
	int temp = L->r[i];
	L->r[i] = L->r[j];
	L ->r[j] = temp;
	};

void BubbleSort(SqList *L)
{
	int i, j;
	for (i = 1; i < L->length; i++)
	{
		for (j = L->length; j >= i; j--) // j从后往前循环
		{
			if (L->r[j] > L->r[j + 1]) // 大于则交换前者与后者
			{
				swap(L, j, j + 1);
			};
		};
	};
};

代码优化,增加一个标记变量flag,避免因已经有序的情况下的无意义的循环判断。

冒泡排序的复杂度:最好情况下O(n-1)次比较=O(n),最坏情况下:O(1+2+3+…+(n-1))==>O(n²)

简单排序

简单选择排序法(Simple Selection Sort): 就是通过n-i次关键字间比较,从n-j+1个记录中选择出关键字最小的记录,并和第i(1≤i≤n)个记录交换。(比较完再交换)

void SelectSort(SqList *L)
{
	int i, j, min;
	for(i=1; i < L->length; i++)
	{
		min = i; //将当前下标定义为最小下标
		for (j = i + 1; j <= L->length; j++)
		{
			if (L->r[min] > L->r[j]) 
				min = j;	//如果有小于当前最小值的关键字,则将下标赋值给min
		};

		if (i != min)	//若min不等于i,则说明找到了最小值的下标
		{
			swap(L, i, min);
		};
	};
}

时间复杂度:比较(n(n-1)/2)+交换(0或者(n-1))=O(n²)

直接插入排序

直接插入排序(Straight Insertion Sort): 将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录增加1的序表。(插入排序)

void InsertSort(SqList *L)
{
	int i, j;
	for (i=2; i <= L->length; i++)
	{
		if (L->r[i] < L->r[i - 1])
		{
			L->r[0] = L->r[i]; //设置哨兵
			for (j = i - 1; L->r[j] > L->r[0]; j--)
				L->r[j + 1] = L->r[j]; //将记录的数据后移
			L->r[j + 1] = L->r[0]; //插入到正确的位置
		};
	};
};

如果排序记录是随机的,平均比较和移动数约为n²/4次,所以时间复杂度:O(n²),但比冒泡排序和简单选择排序性能要好一些。

希尔排序

希尔排序(Shell Sort): 希尔排序算法在时间复杂度上突破O(n²)的第一批算法,在基本有序的序列中(基本有序:就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间),利用“增量”分割子序列使整个序列向基本有序发展,在子序列内分别进行直接插入排序后使得序列有序。

void ShellSort(SqList *L)
{
	int i, j;
	int increment = L->length;

	do
	{
		increment = increment / 3 + 1;
		for (i = increment + 1; i < L->length; i++) 
		{
			if (L->r[i] < L->r[i - increment])
			{//将L->r[i]插入有序增量子表
				L->r[0] = L->r[i]; //设置哨兵,暂存L->r[0]
				for (j = i - increment; j>0 && L->r[0] < L->r[j]; j-=increment)
				{
					L->r[j + increment] = L->r[j]; //向后移动,查找插入位置
				}
				L->r[j + increment] = L->r[0]; //插入
			}
		}

	}
	while (increment>1);
}

堆排序

大顶堆: 每个结点的值大于或等于其左右孩子结点的值;小顶堆: 每个结点的值小于或等于其左右孩子结点的值。

堆排序(Heap Sort): 利用堆进行排序(大顶堆),将排序的序列构造成一个大顶堆,此时堆顶的根节点最大,将它与堆数组的末尾元素交换,然后将剩余的(n-1)个序列重新构造一个堆,就会得到n个元素中次小值,反复执行便能得到一个有序序列。

void HeapAdjust(SqList* L, int s, int m)
{
	int temp, j;
	temp = L->r[s];
	for (j=2*s; j<=m; j*=2) // 沿关键字较大的孩子结点向下筛选
	{
		if (j < m && L->r[j] < L->r[j + 1])
			++j;   //j为关键字中较大的记录的下标
		if (temp >= L->r[j])
			break; //rc应插入在位置s上
		L->r[s] = L->r[j];
		s = j;
	}
	L->r[s] = temp; //插入
}

void HeapSort(SqList* L)
{
	int i;
	for (i=L->length/2; i>0; i--) //把L中r构建成一个大顶堆
	{
		HeapAdjust(L, i, L->length);
	}

	for (i=L->length; i>1; i--)
	{
		swap(L,1,i); //将堆顶记录和当前未经排序子序列的最后一个记录交换
		HeapAdjust(L,1,i-1); //将r[1...i-1]重新调整为大顶堆
	}
}

重构堆,第i次取堆顶需要用O(log i)的时间,需要取(n-1)次堆顶记录,因此重建对的时间复杂度为O(nlogn),即堆排序的时间复杂度为O(nlogn),堆排序对原始记录的排序状态并不敏感,最好最坏和平均时间复杂度都相同,在性能上要远远好于冒泡、简单选择、直接插入的O(n²)的时间复杂度。

归并排序

归并排序(Merging Sort): 利用归并的思想(合并、并入)实现排序,原理是假设初始序列中含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为2或1的有序子序列;再两两归并…直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

void Merge(int SR[], int TR[], int i, int m, int n) //合并两个有序子列
{
	int k, j, l;
	for (j = m + 1, k = i; i <= m && j <= n; k++) // 将SR中记录从小到大归并到TR
	{
		if (SR[i] < TR[j])
			TR[k] = SR[i++]; //先放入再自增
		else
			TR[k] = SR[j++];
	}

	if (i <= m)
	{
		for (l = 0; l <= m - i; l++)
			TR[k + 1] = SR[i + 1]; //将剩余的SR[i..m]复制到TR
	}
	if (j <= n)
	{
		for (l = 0; l <= n - j; j++)
			TR[k + 1] = SR[j + 1]; //将剩余的SR[j..n]复制到TR
	}
}
void MSort(int SR[], int TR1[], int s, int t)
{
	int m;
	int TR2[MAXSIZE + 1];
	if (s == t)
	{
		TR1[s] = SR[t];
	}

	else
	{
		m = (s + t) / 2; //将SR分段
		MSort(SR, TR2, s, m); //通过递归将SR[s..m]归并为有序的TR2[s..m]
		MSort(SR, TR2, m+1, t); //通过递归将SR[m+1..t]归并为有序的TR2[m+1..t]
		Merge(TR2, TR1, s, m, t); //将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t]
	}


}

void MergingSort(SqList* L)
{
	MSort(L->r, L->r, 1, L->length);

}

有序序列进行两两归并,总的时间复杂度为O(nlogn);归并需要同样数量的存储空间存放归并结果+递归时深度为logn的栈,所以总的空间复杂度为O(n+logn);两两比较不存在跳跃,所以归并排序是内存占用高,但效率高且稳定的算法。

快速排序

快速排序(QuickSort): 通过一趟排序将待排的记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可以分别对这两部分记录继续进行排序,已达到整个序列有序的目的。

int Partition(SqList *L, int low, int high) //交换顺序表L中子表记录,使枢轴记录到位,并返回其位置
{
	int pivotkey;
	pivotkey = L->r[low]; //用子表的第一个记录作枢轴记录
	while (low < high) //从表两端交替向中间扫描
	{
		while (low < high && L->r[high] >= pivotkey)
			high--;
		swap(L, low, high); //比枢轴小的记录交换到低位

		while (low < high && L->r[low] <= pivotkey)
			low++;
		swap(L, low, high); //比枢轴大的记录交换到高端
	}
	return low;
}

void QSort(SqList *L, int low, int high)
{
	int pivot;
	if (low < high)
	{
		pivot = Partition(L, low, high);//叫r分为两部分,算出pivot值
		QSort(L, low, pivot); //对低子表递归排序
		QSort(L, pivot, high); //对高子表递归排序
	}
}

void QuickSort(SqList* L)
{
	QSort(L, 1, L->length)
}

时间复杂度分析,在最优情况下,Partition每次都能平均划分,快速排序算法的时间复杂度为O(nlogn);在最坏情况下待排序为逆序或者正序,每次划分只能得到一个比上次少一个记录的子序列,另一个为空最终时间复杂度为O(n²);平均时间复杂度为O(nlogn)。空间复杂度,最优情况下,递归调用栈O(logn);最坏情况下为O(n),平均空间复杂度O(logn)。由于关键字的比较和交换是跳跃的,所以快速排序是一种不稳定的排序方法。

快速排序的优化:

  • 优化选取的枢轴(三数取中),
  • 优化不必要的交换(将枢轴关键字备份到L->r[0],用替换代替swap()),
  • 优化小数组时的排序方案
  • 优化递归操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1YxHKsTN-1614865665987)(F:\电子书及笔记\MarkDown笔记\笔记用图\第七章:图\排序方法比较.jpg)]

度为O(nlogn);在最坏情况下待排序为逆序或者正序,每次划分只能得到一个比上次少一个记录的子序列,另一个为空最终时间复杂度为O(n²);平均时间复杂度为O(nlogn)。空间复杂度,最优情况下,递归调用栈O(logn);最坏情况下为O(n),平均空间复杂度O(logn)。由于关键字的比较和交换是跳跃的,所以快速排序是一种不稳定的排序方法。

快速排序的优化:

  • 优化选取的枢轴(三数取中),
  • 优化不必要的交换(将枢轴关键字备份到L->r[0],用替换代替swap()),
  • 优化小数组时的排序方案
  • 优化递归操作
    在这里插入图片描述
    在这里插入图片描述

标签:排序,记录,第九章,大话,int,length,序列,数据结构,复杂度
来源: https://blog.csdn.net/weixin_42367960/article/details/114378803

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有