数据结构之排序

一.什么是稳定排序?

排序后相等元素的相对位置不发生变化

二.稳定排序有哪些?

2.1.不稳定排序:快速排序、希尔排序、堆排序、选择排序

2.2.稳定排序:冒泡排序、插入排序、二分插入排序、归并排序、基数排序

三.各大排序算法

3.1.稳定算法

3.1.1.冒泡排序
思想:通过两两比较不断将最大的数浮出水面。一次浮出一个数,需要n-1次。
时间复杂度O(n^2)
代码
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100;

void input(int a[],int &n){
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
}

void output(int a[],int n) {
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}

void bubbleSort(int a[], int n) {
	for (int i = 0; i < n - 1; i++)
		for (int j = 0; j < n - i - 1; j++)
			if (a[j] > a[j + 1])
				swap(a[j], a[j + 1]);
}

int main() {
	int a[N];
	int n;
	input(a, n);
	bubbleSort(a, n);
	output(a, n);
	return 0;
}
题目:1、已知待排序记录的关键字序列为 {49, 38, 65, 97, 76, 13, 27, 49},请给出用冒泡排序法进行排序的过程。
i.解答
第一趟:38 49 65 76 13 27 49 97; 
第二趟:38 49 65 13 27 49 76 97;
第三趟:38 49 13 27 49 65 76 97;
第四趟:38 13 27 49 49 65 76 97;
第五趟:13 27 38 49 49 65 76 97;
第六趟:13 27 38 49 49 65 76 97;
第七趟:13 27 38 49 49 65 76 97
ii.程序运行

3.1.2.插入排序
思想:“扑克牌排序”;由于一个数天然有序,所以从第二个数开始,不断将后续的数插入正确的位置。
时间复杂度O(n^2)
代码
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 100;
void input(int a[], int& n) {
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
}

void output(int a[], int n) {
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}

void insertSort(int a[],int n) {
    //因为一个元素天然有序,所以从第二趟开始。
	for (int i = 1; i < n; i++) {
		int value = a[i];
		int pos = i - 1;
		while (pos >= 0 && a[pos] > value) {
			a[pos + 1] = a[pos];
			pos--;
		}
		a[pos + 1] = value;
	}
}

int main() {
	int n;
	int a[N];
	input(a, n);
	insertSort(a, n);
	output(a, n);
	return 0;
}
题目:使用直接插入排序,对数组{5,2,3,9,8,1}进行排序。
i.解答
第一趟:5 2 3 9 8 1;
第二趟:2 5 3 9 8 1;
第三趟:2 3 5 9 8 1;
第四趟:2 3 5 9 8 1;
第五趟:2 3 5 8 9 1;
第六趟:1 2 3 5 8 9;
ii.程序运行

3.1.3.归并排序
思想:“分治法的典型之一”;先分再治;保证分得的每一子块的有序,再对子块间进行合并。
时间复杂度是O(nlogn)
具体的我们以一组无序数列{14,12,15,13,11,16}为例分解说明,如下图所示:

##### 代码
#include<iostream>

using namespace std;

const int N = 100;

void input(int a[], int& n) {
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
}

void output(int a[], int n) {
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}

void merge(int a[], int left, int mid, int right) {
	int l_pos = left, r_pos = mid + 1;
	int pos = left;
	int temp[N];
	while (l_pos <= mid && r_pos <= right) {
		if (a[l_pos] < a[r_pos]) temp[pos++] = a[l_pos++];
		else temp[pos++] = a[r_pos++];
	}
	while (l_pos <= mid) 
		temp[pos++] = a[l_pos++];
	while (r_pos <= right) 
		temp[pos++] = a[r_pos++];
	while (left <= right) {
		a[left] = temp[left];
		left++;
	}
}

void mergeSort(int a[],int left,int right) {
	if (left < right) {
		int mid = (left + right) / 2;
		mergeSort(a, left, mid);
		mergeSort(a, mid + 1, right);
		merge(a, left, mid, right);
	}
}

int main() {
	int a[N];
	int n;
	input(a, n);
	mergeSort(a, 0, n - 1);
	output(a, n);
	return 0;
}
题目:已知待排序记录的关键字序列为{49, 38, 65, 97, 76, 13, 27}, 给出用2-路归并排序法进行排序的过程。
i.解答
第一趟:38 49 65 97 13 76 27;
第二趟:38 49 65 97 13 27 76;
第三趟:13 27 38 49 65 76 97;
ii.程序运行
迭代版归并排序
void mergeSort() {
	for (int curr_size = 1; curr_size <= n - 1; curr_size = 2 * curr_size) {
		for (int left = 0; left < n - 1; left += 2 * curr_size) {
			int mid = min(left + curr_size - 1, n - 1);
			int right = min(left + 2 * curr_size - 1, n - 1);
			int l_pos = left;
			int r_pos = mid + 1;
			int pos = left;
			while (l_pos <= mid && r_pos <= right) {
				if (a[l_pos] < a[r_pos]) temp[pos++] = a[l_pos++];
				else temp[pos++] = a[r_pos++];
			}
			while(l_pos <= mid) temp[pos++] = a[l_pos++];
			while(r_pos <= right) temp[pos++] = a[r_pos++];
			for (int i = left; i <= right; i++)
				a[i] = temp[i];
		}
		print(count++);
	}
}

3.2.不稳定算法

3.2.1.堆排序
建堆的时间复杂度是O(n)
维护堆的性质的时间复杂度是O(logn)
整体时间复杂度是O(nlogn)
代码
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100;

void input(int a[],int& n) {
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
}

void output(int a[], int n) {
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}

void heapify(int a[],int n,int pos) {
	int l_child = pos * 2 + 1;
	int r_child = pos * 2 + 2;
	int max = pos;
	if (a[max] < a[l_child] && l_child < n) max = l_child;
	if (a[max] < a[r_child] && r_child < n) max = r_child;
	if (max != pos) {
		swap(a[max], a[pos]);
		heapify(a, n, max);
	}
}

void heapSort(int a[], int n) {
	//建堆
	for (int i = n / 2 - 1; i >= 0; i--)
		heapify(a, n, i);
	//排序
	for (int i = n - 1; i > 0; i--) {
		swap(a[0], a[i]);
		heapify(a, i, 0);//维护大顶堆的性质
	}
}

int main() {
	int n;
	int a[N];
	input(a, n);
	heapSort(a, n);
	output(a, n);
	return 0;
}
对于建堆
对于叶子节点而言,已经天生满足大/小顶堆的性质,因此要从最后一个父节点往前依次作堆调整,当然每次堆调整,还需递归地调整其子节点的堆性质。
题目:已知无序序列为{49, 38, 65, 97, 76, 13, 27, 49},用 "筛选法” 将其调整为个大根堆,给出建堆的过程。
i.解答

ii.程序运行

对于排序
对于已经形成的大/小顶堆,将堆尾元素和根节点(下标为0)交换,然后将堆尾元素从堆中去掉,继续维护堆的性质,然后递归这些操作。
有序数列每次增加一个元素,因此需要交换n-1次。
题目:已知待排序记录的关键字序列为 {49, 38, 65, 97, 76, 13, 27, 49},给出用堆排序法进行排序的过程。
i.解答

ii.程序运行

3.2.2.快速排序
时间复杂度是O(nlogn)
思想:“一种典型的分治法排序”,与归并排序的先分后治不同,快排是先治后分,保证小于标准值的数一定在标准值的左边,大于标准值的数在标准值的右边,保证相对的有序,再对标准值左右两边进行相同处理。
代码
void quickSort(int* a,int left,int right) {
	if (left < right) {
		int pivot = a[left];
		int l_pos = left - 1;
		int r_pos = right + 1;
		while (l_pos < r_pos) {
			do l_pos++; while (a[l_pos] < pivot);
			do r_pos--; while (a[r_pos] > pivot);
			if (l_pos < r_pos) swap(&a[l_pos], &a[r_pos]);
		}
		quickSort(a, left, r_pos);
		quickSort(a, r_pos + 1, right);
	}
}
题目:已知待排序记录的关键字序列为 {49,38,6 5,97,76,13,27, 49}, 请给出用快速排序法进行排序的过程。
严蔚敏老师的解答:

3.2.3.选择排序
时间复杂度O(n^2)
思路(若要求从小到大):找到第一个位置符合要求的,依次类推,每一趟有序数列会增加一个数。需要n-1趟
void selectSort() {
	for (int i = 0; i < n - 1; i++) {
		for (int j = i + 1; j < n; j++)
			if (a[i] > a[j])
				swap(&a[i], &a[j]);
		print(i + 1);
	}
}
题目:对数组{9,1,1,3,2,4,5,8,7}进行选择排序
第一趟:1 9 1 3 2 4 5 8 7;
第二趟:1 1 9 3 2 4 5 8 7;
第三趟:1 1 2 9 3 4 5 8 7;
第四趟:1 1 2 3 9 4 5 8 7;
第五趟:1 1 2 3 4 9 5 8 7;
第六趟:1 1 2 3 4 5 9 8 7;
第七趟:1 1 2 3 4 5 7 9 8;
第八趟:1 1 2 3 4 5 7 8 9;

posted @ 2023-11-06 13:06  彭乐祥  阅读(48)  评论(0编辑  收藏  举报