排序算法总结

排序的稳定性:
假设 k i = k j k_i=k_j ki=kj,且在排序序列中 r i r_i ri领先于 r j r_j rj,如果排序后 r i r_i ri仍领先于 r j r_j rj,则排序方法是稳定的,否则,排序是不稳定的。

1.冒泡排序法

版本一

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++)cin >> v[i];
	for (int i = 0; i < n; i++)
	{
		for (int j = i + 1; j < n ; j++)
		{
			if (v[i] > v[j])
				swap(v[i], v[j]);
		}
	}
	for (auto i : v)
		cout << i << " ";
}

通过比较当前下标与下标以后的所有元素,确定当前的最小值,每一个i所在的循环均可以确定一个最小值。

版本二

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++)cin >> v[i];
	for (int i = 0; i < n; i++)
	{
		for (int j = n - 2; j >= i; j--)
		{
			if (v[j] > v[j + 1])
				swap(v[j], v[j + 1]);
		}
	}
	for (auto i : v)
		cout << i << " ";
}

从最后一项开始,两两进行比较(第j项与第j+1项进行比较),如果前一项大于后一项,前一项则为较小者,直到循环到i,确定最小值。

复杂度分析:
数组长度为n,第i次循环需要进行,n-i-1 次比较
∑ i = 0 n − 1 ( n − i − 1 ) = ( n − 1 ) ∗ n 2 = O ( n 2 ) \sum_{i=0}^{n-1}(n-i-1) =\frac{(n-1)*n}{2} =O(n^2) i=0n1(ni1)=2(n1)n=O(n2)

2. 简单选择排序

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++)cin >> v[i];
	for (int i = 0; i < n; i++)
	{
		int mini = i;
		for (int j = i+1; j < n; j++)
		{
			if (v[mini] > v[j])
				mini = j;
		}
		if (i != mini)
			swap(v[mini], v[i]);
	}
	for (auto i : v)
		cout << i << " ";
}

与冒牌排序相比,减少了交换的次数,效率略高于冒泡排序。第i次循环确定第i个最小值,与后续n-i-1个元素进行比较。
复杂度分析:
数组长度为n,第i次循环需要进行,n-i-1 次比较
∑ i = 0 n − 1 ( n − i − 1 ) = ( n − 1 ) ∗ n 2 = O ( n 2 ) \sum_{i=0}^{n-1}(n-i-1) =\frac{(n-1)*n}{2} =O(n^2) i=0n1(ni1)=2(n1)n=O(n2)

3. 直接插入排序

将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数增1的有序表。

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++)cin >> v[i];
	int j;
	for (int i = 1; i < n; i++)
	{
		if (v[i] < v[i - 1])
		{
			int temp = v[i];
			// 存较小的数
			for (j = i - 1; v[j] >= temp; j--)
			{
				v[j + 1] = v[j];
				if (j == 0)
				{
					j--;
					break;
				}
			}
			v[j+1] = temp;
		}

	}
	for (auto i : v)
		cout << i << " ";
}

这个排序算法,个人感觉有点绕。先判断对于第i个数与i-1个数的大小关系,如果 a[i]<a[i-1] ,说明a[i-1]位于a[i]之前,所以从 j=i-1开始,循环至找到一个数小于a[i]停止,a[j] = a[j-1],第j-1的位置上的数向前挪一位置,再将一开始保存的a[i]存入a[j+1]中

复杂度分析:
下标从1开始
若本身就是有序,则只需要比较n-1次。
若完全逆序,对于第i个数,前面有i-1个数,同时加上temp共i个数,需要比较i次,因为从i = 2开始比较 ∑ i = 2 n = ( n + 2 ) ( n − 1 ) 2 \sum_{i=2}^{n}=\frac{(n+2)(n-1)}{2} i=2n=2(n+2)(n1)

以上是时间复杂度为 O ( n 2 ) O(n^2) O(n2)的排序算法。接下来将要介绍改进的排序算法。

4.希尔排序

希尔排序就是以一个增量(该增量逐渐递减至一)进行插入排序。
该实例以增量序列: i n c r e m e n t / 3 + 1 increment / 3+1 increment/3+1

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	cin >> n;
	vector<int> v(n+1);
	for (int i = 1; i <= n; i++)cin >> v[i];
	int increment = n;
	// 初始增量为数组长度
	do
	{
		increment = increment / 3 + 1;
		// 增量计算4->2->1
		//从increment至n 进行插入排序
		// t从increment+1 开始保证 t-increment存在
		for (int t = increment + 1; t <= n; t++)
		{
			if (v[t - increment] > v[t])
			{
				v[0] = v[t];
				int j;
				// 从t-increment 开始,以increment为增量进行插入排序
				// 不断进行循环
				for (j = t - increment; j >0 &&v[j] > v[0]; j -= increment)
				{
					v[j + increment] = v[j];
				}
				// 退出循环时是位置j是小于v[0]的
				v[j + increment] = v[0];
			}
		}
	} while (increment>1);
	for (int i = 1; i <= n; i++)
		cout << v[i] << " ";
}

希尔排序是不稳定的排序方法,因为如果两个相等的元素存在,因为交换是跳跃式交换,很有可能他们的相对位置发生改变。

时间复杂度分析:根据增量序列不同,时间复杂度也不同,最好情况 O ( N 1.3 ) O(N^{1.3}) O(N1.3),最坏情况是 O ( N 2 ) O(N^{2}) O(N2),平均情况 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N))~ O ( N 2 ) O(N^{2}) O(N2)

5.堆排序

堆:堆是一个完全二叉树,每个节点的的值均大于或等于其左右孩子的值被称为大顶堆。每个节点的值均小于等于其左右孩子的值被称为小顶堆。

根节点标号为1,设有n个节点,则数的高度为 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor \log_2n \rfloor + 1 log2n+1 ,若节点的标号为k,且存在左右子节点,则左子标号为 2 k 2k 2k,右子标号为 2 k + 1 2k+1 2k+1

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
void heapadjust(vector<int>& v, int index,int length)
{
	int temp = v[index];
	// 暂存需要交换的节点
	for (int i = index * 2; i <= length; i *= 2)
	{
		if (i<length&&v[i] < v[i + 1])
			i++;
		// i==length 只有左子
		if (temp >= v[i])
			break;
		v[index] = v[i];
		index = i;
		// 交换后的节点位置
	}
	v[index] = temp;
}
int main()
{
	int n;
	cin >> n;
	vector<int>v(n+1);
	for (int i = 1; i <= n; i++)
		cin >> v[i];
	int length = v.size() - 1;
	for (int i = n / 2; i >= 1; i--)
		heapadjust(v, i,length);
	for (int i = length; i > 1; i--)
	{
		swap(v[1], v[i]);
		heapadjust(v, 1, i - 1);
	}
	for (int i  = 1;i<=n;i++)
		printf("%d ", v[i]);
}

因为堆排序是跳跃式的比较,所有堆排序也是不稳定的。
构建堆需要 O ( n ) O(n) O(n)的时间复杂度,因为共有 n 2 \frac{n}{2} 2n个非叶子节点,每个非叶子节点只需要与其左右子比较。

在在正式排序时,第i次取堆顶记录需要 log ⁡ ( n − i ) = log ⁡ ( n ) \log(n-i)=\log(n) log(ni)=log(n)的时间,共需要n次,所以总时间复杂度为 O ( n log ⁡ ( n ) ) O(n\log(n)) O(nlog(n))

上面堆排序是通过非递归算法进行的,且下标从一开始。那么如果我们用递归的方法,且下标从零开始怎么办呢?

#include<iostream>
#include<vector>
using namespace std;
void heapadjust(vector<int>& v,int posi,int last)
{
	// posi 为当前调整的位置
	// last 为数组中最后一个元素的位置
	if (posi < 0)
		return;
	int larger_posi = posi,left = posi * 2 + 1, right = posi * 2+2;
	if (left <= last && v[larger_posi] < v[left])
		larger_posi = left;
	if (right <= last && v[larger_posi] < v[right])
		larger_posi = right;
	swap(v[posi], v[larger_posi]);
	heapadjust(v, posi - 1 ,last);
}
int main()
{
	int n;
	cin >> n;
	vector<int>v(n);
	for (int i = 0; i < n; i++) cin >> v[i];
	heapadjust(v, (n-2) / 2, n-1);
	for (auto i : v)
		cout << i << " ";
	cout << endl;
	for (int i = n - 1; i >= 0; i--)
	{
		swap(v[0], v[i]);
		heapadjust(v, (i- 1) / 2, i - 1);
	}
	for (auto i : v)
		cout << i << " ";
}

如果下标从0开始,数组长度为n,那么最后一个非叶子节点的下标为 ⌊ n − 2 2 ⌋ \lfloor\frac{n-2}{2}\rfloor 2n2
且对于一个非叶子节点如果下标从零开始,该节点的下标为x,则其左子为 2 ∗ x + 1 2*x+1 2x+1,其右子为 2 ∗ x + 2 2*x+2 2x+2

6.归并排序

归并排序就是不断的将数组进行二分,直到分成的子数组只剩下一个元素,之后合并两个元素使之成为有序的数组的过程。

#include<iostream>
#include<vector>
using namespace std;
const int N = 1e6 + 10;
int q[N];
int temp[N];
void mergesort(int l, int r, int q[])
{
	if (l >= r)
		return;
	int mid = l + r >> 1;
	mergesort(l, mid, q);
	mergesort(mid + 1, r, q);
	int k=0,i = l, j = mid + 1;
	while (i <= mid && j <= r)
		if (q[i] <= q[j]) temp[k++] = q[i++];
		else temp[k++] = q[j++];
	while (i <= mid) temp[k++] = q[i++];
	while (j <= r) temp[k++] = q[j++];

	for (int i = l, j = 0; i <= r; i++)
		q[i] = temp[j++];
}
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) cin >> q[i];
	mergesort(0, n - 1, q);
	for (int i = 0; i < n; i++) printf("%d ", q[i]);
}

时间复杂度分析,数组长度为n,需要进行 ⌊ log ⁡ n ⌋ + 1 \lfloor\log{n}\rfloor + 1 logn+1次拆分数组,每次拆分完后合并数组需要花费 O ( n ) O(n) O(n)的时间复杂度,所以时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

7.快速排序

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int a[10], b[10];
void quick_sort(vector<int>& v, int l, int r)
{
	if (l >= r)
		return;
	int i = l - 1, j = r + 1;
	int mid = v[l];
	while (i < j)
	{
		do i++; while (v[i] < mid);
		do j--; while (v[j] > mid);
		if (i < j) swap(v[i], v[j]);
	}
	quick_sort(v, l, i);
	quick_sort(v, i + 1, r);
}
int main()
{
	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++) cin >> v[i];
	quick_sort(v, 0, n - 1);
	for (auto i : v)
		printf("%d ", i);
}

模板。如果选取枢纽元为左边界,则递归时为(l,i),(i+1,r)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Agreenhan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值