数据结构之排序

1.冒泡排序

1.1 非递归实现

  • 核心代码:
void fun(int arr[],int n) {
	for (int i = n - 1; i > 0; i--) {
		for (int j = 0; j <= i; j++) {
			if (arr[j] > arr[j+1]) {
				int tmp = arr[j];
				arr [j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

1.2 递归实现

  • 核心代码:
void fun(int arr[],int n) {
	if (n < 0) return ;
	for (int j = 0; j < n; j++) {
		if (arr[j] > arr[j + 1]) {
			int tmp = arr[j];
			arr[j] = arr[j + 1];
			arr[j + 1] = tmp;
		}
	}
	fun(arr, n - 1);
}

1.3 复杂度和稳定性

  • 时间复杂度为O(N^2),空间复杂度为O(1)。
  • 如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

2.选择排序

2.1 非递归实现

  • 核心代码:
void fun(int arr[], int n) {
	for (int i = 0; i < n - 1; i++) {
		int minlocation = i;
		for(int j = i + 1; j < n; j++) {
			if(arr[j]  < arr[minlocation]) {
				minlocation = j;
			}
		}
		if(minlocation != i) {
			swap(arr[i], arr[minlocation]);
		}
	}
}

2.2 递归实现

  • 核心代码:
void fun(int arr[], int start, int n) {
	if (start > =n) return ;
	
	int minlocation=start;
	for (int j = start + 1; j < n; j++) {
		if (arr[j] < arr[minlocation]) {
				minlocation = j;
			}
	}
	if(minlocation != i) {
		swap(arr[i], arr[minlocation]);
	}
		
	fun(arr, start + 1, n);
}

2.3 复杂度和稳定性

  • 时间复杂度为O(N^2),空间复杂度为O(1)。
  • 在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等 的元素后面,那么交换后稳定性就被破坏了。举个例子序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

3.插入排序

3.1 非递归实现

  • 核心代码:
void fun(int arr[], int n) {
	for (int i = 1; i < n; i++) {
		int tmp = arr[i];
		int j = i;
		while (j - 1 >= 0 && arr[j - 1] > tmp) {
			arr[j] = arr[j - 1];
			j--;
		}
		arr[j] = tmp;
	}
}

3.2 递归实现

  • 核心代码:
void fun(int arr[], int start, int n) {
	if (start >= n) return ;
	
	int tmp = arr[start];
	int j = start;
	while (j - 1 >= 0 && arr[j - 1] > tmp){
		arr[j] = arr[j - 1];
		j--;
	}
	arr[j] = tmp;
	
	fun(arr, start + 1, n);
}

3.3 复杂度和稳定性

  • 时间复杂度为O(N^2),空间复杂度为O(1)。
  • 比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比较。如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面,也就是说相等元素的前后顺序没有改变,所以插入排序是稳定的。

4.希尔排序

4.1 非递归实现

  • 核心代码:
void fun1(int arr[], int start, int d, int n) {
	for(int i = start + d; i < n; i = i + d) {
		int tmp = arr[i];
		int j = i;
		while (j - d >= start && arr[j - d] > arr[j]) {
			arr[j] = arr[j-d];
			j = j - d;
		}
		arr[j] = tmp;
	}
}
void fun(int arr[], int n) {
	int d = n;
	while (d / 2 >= 1) {
		d = d / 2;
		for (int i = 0; i < d; i++) {
			fun1(arr, i, d, n);
		}
	}
}

4.2 复杂度和稳定性

  • 时间复杂度小于O(N^2),空间复杂度为O(1)。
  • 由于多次插入排序,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在多次插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的排序算法。

5.快速排序

5.1 非递归实现

  • 核心代码:
void fun(int arr[], int n) {
	stack<pair<int, int>> st;
	st.push({0, n - 1});
	while (!st.empty()) {
		pair<int, int> p = st.top();
		st.pop();
		int l = p.first, r = p.second;
		int i = l, j = r;
		int tmp = arr[l];
		while (i < j) {
			while (i < j && tmp <= arr[j]) j--;
			arr[i] = arr[j];
			while ( i< j && arr[i] <= tmp) i++;
			arr[j] = arr[i];
		}
		arr[i] = tmp;
		if(i + 1 < r ) {
			st.push({i + 1, r});
		}
		if(l < i - 1) {
			st.push({l, i - 1});
		}
	}
}

5.2 递归实现

  • 核心代码:
void fun(int arr[], int l, int r) {
	if (l >= r) return ;
	int i = l, j = r;
	int tmp = arr[l];
	//int k = l + rand() % (r - l + 1);
	//swap(arr[l], arr[k]);
	while (i < j) {
		while (i < j && tmp <= arr[j]) j--;
		arr[i] = arr[j];
		while (i < j && arr[i] < =tmp) i++;
		arr[j] = arr[i];
	}
	arr[i] = tmp;
	fun(arr, l, i - 1);
	fun(arr, i + 1, r);
}

5.3 复杂度和稳定性

  • 时间复杂度:O(nlogn);空间复杂度:快速排序使用递归,递归使用栈,因此它的空间复杂度为O(logn)。
  • 快速排序无法保证相等的元素的相对位置不变,因此它是不稳定排序算法。

5.4 随机快速优化

  • 基本的快速排序选取第一个或者最后一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。

6.归并排序

6.1 递归实现

  • 核心代码:
void merge(int arr[], int l, int r) {
	int temp[r - l + 1];
	int mid = (l + r) / 2;
	int i = l, j = mid + 1;
	int k = 0;
	while (i <= mid && j <= r) {
		if (arr[i] <= arr[j]) {
			temp[k++] = arr[i++];
		} else {
			temp[k++] = arr[j++];
		}
	}
	while(i <= mid)
		temp[k++] = arr[i++];
	while(j <= r)
		temp[k++] = arr[j++];
	for(i = l, k = 0;i <= r; i++, k++){
		arr[i] = temp[k];
	}
}
void sort(int arr[], int l, int r) {
	if(l < r){
		int mid = (l + r) / 2;
		sort(arr, l, mid);
		sort(arr, mid + 1, r);
		merge(arr, l, r);
	}
}

6.2 复杂度和稳定性

  • 每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
  • 在合并操作的时候,相等元素并不会改变相对位置,因此归并排序是稳定排序。

7.堆排序

7.1 非递归实现

  • 核心代码:
void adjustHeap(int arr[], int start, int n) {
	int tmp = arr[start];
	for(int i = 2 * start + 1; i < n; i = 2 * i + 1) {
		if (i + 1 < n && arr[i] < arr[i + 1]) {
			i = i + 1;
		}
		if (arr[i] > tmp) {
			arr[start] = arr[i];
			start = i;
		}else{
			break;
		}
	}
	arr[start] = tmp;
}
void sort(int arr[], int n) {
	for(int i = n / 2 - 1; i >= 0; i--){
		adjustHeap(arr, i, n);
	}
	for(int j = n - 1; j >= 0; j--) {
		swap(arr[0], arr[j]);
		adjustHeap(arr, 0, j);
	}
}

7.2 复杂度和稳定性

  • 时间复杂度O(nlogn),空间复杂度O(1)。
  • 在堆调整操作的时候,相等元素不能保证不改变相对位置,因此堆排序是不稳定排序。

8.基数排序vs计数排序vs桶排序

  • 这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

1)基数排序:根据键值的每位数字来分配桶;
2)计数排序:每个桶只存储单一键值;
3)桶排序:每个桶存储一定范围的数值;

9.算法总结

10.排序的相关应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值