数据结构笔记——第八章 排序

目录

8 排序

8.1 排序的基本概念

8.1.1 排序的定义

8.2 插入排序

8.2.1 直接插入排序

 8.2.2 希尔排序

8.3 交换排序

8.3.1 冒泡排序

8.3.2 快速排序

8.4 选择排序

8.4.1 简单选择排序

8.4.2 堆排序

8.4.3 堆的插入删除

8.5 归并排序和基数排序

8.5.1 归并排序

8.5.2 基数排序

8.6 外部排序

8.6.1 外部排序的基本概念

8.6.2 败者树

8.6.3 置换—选择排序

8.6.4 最佳归并树


8 排序

8.1 排序的基本概念

8.1.1 排序的定义

排序:就是重新排列表中的元素,使表中的元素满足按关键字有序的过程。

算法的稳定性:若待排序表中有两个元素Ri和Rj,其对应的关键字相同即keyi=keyj,且在排序前Ri在Rj的前面,若使用某一排序算法排序后,Ri仍然在Rj的前面,则称这个排序算法是稳定的,否则称排序算法是不稳定的。

排序算法的分类:

内部排序:数据都在内存中

外部排序:数据太多,无法全部放入内存

8.2 插入排序

8.2.1 直接插入排序

算法思想:每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到完全记录插入完成。

算法实现:

void InsertSort(int A[], int n) {
	int i, j, temp;
	for (i = 1; i < n; i++) {
		if (A[i] < A[i - 1]) {
			temp = A[i];
			for (j = i - 1; j >= 0 && A[j] > temp; --j)
				A[j + 1] = A[j];
			A[j + 1] = temp;
		}
	}
}

优化——折半插入排序

思路:先用折半查找找到应该插入的位置

void InsertSort(int A[], int n) {
	int i, j, low, high, mid;
	for (i = 2; i <= n; i++) {
		A[0] = A[i];
		low = 1; high = i - 1;
		while (low <= high) {
			mid = (low + high) / 2;
			if (A[mid] > A[0])
				high = mid + 1;
		}
		for (j = i - 1; j >= high + 1; --j)
			A[j + 1] = A[j];
		A[high + 1] = A[0];
	}
}

对链表进行插入排序

移动元素的次数变少了,但是关键字对比的次数依然还是O(n^{2})数量级,整体来看时间复杂度依然是O(n^{2})

 8.2.2 希尔排序

先追求表中元素部分有序,再逐渐逼近全局有序

void ShellSort(int A[], int n) {
	int d, i, j;
	for(d=n/2;d>=1;d=d/2)
		for(i=d+1;i<=n;++i)
			if (A[i] < A[i - d]) {
				A[0] = A[i];
				for (j = i - d; j > 0 && A[0] < A[j]; j -= d)
					A[j + d] = A[j];
				A[j + d] = A[0];
			}
}

时间复杂度:和增量序列的选择有关,目前无法用数学手段证明确切的时间复杂度

最坏时间复杂度为O(n^{2}),当n在某个范围内时,可达O(n^{1.3})

适用性:仅适用于顺序表,不适用于链表

8.3 交换排序

8.3.1 冒泡排序

基于“交换”的排序:根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置

从后往前(或从前往后)两两比较相邻元素的值,若为逆序,则交换它们,直到序列比较完。称这样的过程为 “一趟”冒泡排序。

算法实现:

void swap(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

void BubbleSort(int A[], int n) {
	for (int i = 0; i < n - 1; i++) {
		bool flag = false;
		for(int j=n-1;j>i;j--)
			if (A[j - 1] > A[j]) {
				swap(A[j - 1], A[j]);
				flag = true;
			}
		if (flag == false)
			return;
	}
}

冒泡排序可以适用于链表

8.3.2 快速排序

算法思想:在待排序表L中任取一个元素pivot作为枢轴(或基准,通常取首元素),通过一趟排序表划分为独立的两部分,使得前者中的所有元素小于pivot,后者中的所有元素大于等于pivot,则pivot放在了其最终位置上,这个过程称为一次“划分”。然后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素为空为止,即所有元素放在了其最终位置上。

算法实现:

//用第一个元素将待排序序列划分成左右两个部分
int Partition(int A[], int low, int high) {
	int pivot = A[low];
	while (low < high) {
		while (low < high && A[high] >= pivot)
			--high;
		A[low] = A[high];
		while (low < high && A[low] <= pivot)
			++low;
		A[high] = A[low];
	}
	A[low] = pivot;
	return low;
}

//快速排序
void QuickSort(int A[], int low, int high) {
	if (low < high) {
		int pivotpos = Partition(A, low, high);
		QuickSort(A, low, pivotpos - 1);
		QuickSort(A, pivotpos + 1, high);
	}
}

快速排序算法优化思路:尽量选择可以把数据中分的枢轴元素。

eg:选头中尾三个位置的元素,取中间值作为枢轴元素或者随机选一个元素作为枢轴元素。

8.4 选择排序

8.4.1 简单选择排序

选择排序:每一趟在待排序元素中选取关键字最小(或最大)的元素加入有序子序列

算法实现:

void SelectSort(int A[], int n) {
	for (int i = 0; i < n - 1; i++) {
		int min = i;
		for (int j = i + 1; j < n; j++) {
			if (A[j] < A[min])
				min = j;
		}
		if (min != i)
			swap(A[i], A[min]);
	}
}

8.4.2 堆排序

大根堆:完全二叉树中,根>=左,右

小根堆:完全二叉树中,根<=左,右

思路:把所有非终端结点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整

检查当前结点是否满足根>=左,右 若不满足,将当前结点与更大的一个孩子互换。若元素互换破坏了下一级的堆,则采取相同的方法继续往下调整(小元素不断“下坠”)

代码实现:

void BuildMaxHeap(int A[], int len) {
	for (int i = len / 2; i > 0; i--)
		HeadAdjust(A, i, len);
}

void HeadAdjust(int A[], int k, int len) {
	A[0] = A[k];
	for (int i = 2 * k; i <= len; i *= 2) {
		if (i < len && A[i] < A[i + 1])
			i++;
		if (A[0] >= A[i])
			break;
		else {
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
}

基于大根堆进行排序

堆排序:

每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换),并将待排序元素序列再次调整为大根堆(小元素不断“下坠”)

基于“大根堆”的堆排序得到“递增序列”

代码实现:

void HeapSort(int A[], int len) {
	BuildMaxheap(A, len);
	for (int i = len; i > 1; i--) {
		swap(A[i], A[1]);
		HeadAdjust(A, 1, i - 1);
	}
}

8.4.3 堆的插入删除

在堆中插入新元素

对于小根堆,新元素放到表尾,与父节点对比,若新元素比父节点更小,则将二者互换。新元素就这样一路“上升”,直到无法继续上升为止

在堆中删除元素

被删除的元素用堆底元素代替,然后让该元素不断“下坠”,直到无法下坠为止

8.5 归并排序和基数排序

8.5.1 归并排序

把两个或多个已经有序的序列合并成一个

代码实现:

int* B = (int*)malloc(n * sizeof(int));

void Merge(int A[], int low, int mid, int high) {
	int i, j, k;
	for (k = low; k <= high; k++)
		B[k] = A[k];
	for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
		if (B[i] <= B[j])
			A[k] = B[i++];
		else
			A[k] = B[j++];
	}
	while (i <= mid)
		A[k++] = B[i++];
	while(j<=high)
		A[k++] = B[j++];
}
void MergeSort(int A[], int low, int high) {
	if (low < high) {
		int mid = (low + high) / 2;
		MergeSort(A, low, mid);
		MergeSort(A, mid + 1, high);
		Merge(A, low, mid, high);
	}
}

8.5.2 基数排序

假设长度为n的线性表中每个结点a_{j}的关键字由d元组(k_{j}^{d-1},k_{j}^{d-2},k_{j}^{d-3},...,k_{j}^{1},k_{j}^{0})组成,其中,

0\leq k_{j}^{i}\leq r-1(0\leq j< n,0\leq i\leq d-1),r称为“基数”

基数排序得到递增序列的过程如下,

初始化:设置r个空队列,Q_{0},Q_{1},...,Q_{r-1}

按照各个关键字位权重递增的次序,对d个关键字位分别做“分配”和“收集”

分配:顺序扫描各个元素,若当前处理的关键字位=x,则将元素插入Q_{x}队尾

收集:把Q_{0},Q_{1},...,Q_{r-1}各个队列中的结点依次出队并链接

数据结构笔记——第八章 排序_第1张图片

8.6 外部排序

8.6.1 外部排序的基本概念

外部排序:数据元素太多,无法一次全部读入内存进行排序

使用“归并排序”的方法,最少只需要在内存中分配3块大小的缓冲区即可对任意一个大文件进行排序

“归并排序”要求各个子序列有序,每次读入两个块的内容,进行内部排序后写回磁盘

采用多路归并可以减少归并趟数,从而减少磁盘读写次数

多路归并负面影响:

内存开销增加,内存归并所需时间增加

数据结构笔记——第八章 排序_第2张图片

8.6.2 败者树

败者树——可视为一棵完全二叉树。k个叶结点分别是当前参加比较的元素,非叶子结点用来记忆左右子树中的“失败者”,而让胜者往上继续进行比较,一直到根结点。

8.6.3 置换—选择排序

数据结构笔记——第八章 排序_第3张图片

8.6.4 最佳归并树

数据结构笔记——第八章 排序_第4张图片 

 

你可能感兴趣的:(数据结构与算法,数据结构,排序算法,算法)