算法的图文讲解"/>
C++ 排序算法的图文讲解
目录
1.冒泡排序
时间复杂度:
2.选择排序
时间复杂度:
3.插入排序
时间复杂度 :
4.计数排序(桶排序)
时间复杂度:
5.堆排序
第一步:
第二步:
第三步:
时间复杂度:
1.冒泡排序
冒泡排序就是通过相邻的元素不断的重复交换将最大的元素排到末尾。
首先是10和40进行比较,40>10,所以不需要交换。然后40和20比较,需要交换,依次对比直到数组有序。
#include <iostream>
#include <vector>using namespace std;void bubbleSort(vector<int>& arr) {int n = arr.size();for (int i = 0; i < n-1; i++) {for (int j = 0; j < n-i-1; j++) {if (arr[j] > arr[j+1]) {// Swap arr[j] and arr[j+1]int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}
}int main() {vector<int> arr = {64, 34, 25, 12, 22, 11, 90};for (int num : arr) {cout << num << " ";}bubbleSort(arr);for (int num : arr) {cout << num << " ";}return 0;
}
时间复杂度:
最好的情况下就是O(n),不需要交换,比较n-1次就可以。
最坏的情况就是O(n²),排序表是逆序的情况,此时需要比较次,并作等数量级的记录移动。
2.选择排序
每次从待排序的元素中选择最小或最大的元素,放在已排序的末尾,直到所有元素都排序完成。
当我们去购物时,假设我们想购买一件T恤,商店里有多种样式和价格不同的T恤供选择。我们可以使用选择排序的思路来进行选择:
- 看见第一个T恤为当前最便宜的。
- 再看剩余的T恤,如果找到一个更便宜的T恤,就后找到的为最便宜的T恤。
- 看完所有的T恤后,我们就找到了最便宜的T恤,将其选择为购买的。
- 重复以上步骤,但这次从剩余的T恤中选择次便宜的,继续寻找下一个购买对象。
- 重复这个过程,直到所有的T恤都被选择完毕。
把数组当成两个来处理,每次i下标代表的地址就是最小的,把他当成已经排列好的数组,然后用j遍历未排序数组,与i进行比较,如果小于i就进行swap,直到未排序数组为空。
#include <iostream>
#include <vector>using namespace std;void selectionSort(vector<int>& arr) {int n = arr.size();for (int i = 0; i < n-1; i++) {int minIdx = i;for (int j = i+1; j < n; j++) {if (arr[j] < arr[minIdx]) {minIdx = j;}}if (minIdx != i) {swap(arr[i], arr[minIdx]);}}
}int main() {vector<int> arr = {64, 34, 25, 12, 22, 11, 90};for (int num : arr) {cout << num << " ";}selectionSort(arr);for (int num : arr) {cout << num << " ";}return 0;
}
时间复杂度:
当待排序的数组已经是有序的时候,在每次迭代中,找到最小元素的过程只需遍历一次未排序部分。因此,最好情况下选择排序的时间复杂度仍然是O(n²),其中n是待排序元素的数量。
当待排序的数组是逆序的时候,在每次迭代中,找到最小元素的过程需要遍历未排序部分,并且需要执行交换操作将最小元素放置在已排序部分的末尾。因此,最坏情况下选择排序的时间复杂度仍然是O(n²)。选择排序法比冒泡和排序的性能要好一些。
3.插入排序
将待排序数组分为有序表和无序表,每次从无序表中取出一个元素,插入到有序表的适当为位置。—开始有序表是一个数,无序表是n-1个数。
每遍历一次,有序表中元素个数增加一个,无序表中元素个数减少一个,重复n-1次,完成排序。
#include <iostream>
#include <vector>using namespace std;void insertionSort(vector<int>& arr) {int n = arr.size();for (int i = 1; i < n; i++) {int key = arr[i];int j = i - 1;while (j >= 0 && arr[j] > key) {arr[j+1] = arr[j];j--;}arr[j+1] = key;}
}int main() {vector<int> arr = {64, 34, 25, 12, 22, 11, 90};for (int num : arr) {cout << num << " ";}insertionSort(arr);for (int num : arr) {cout << num << " ";}return 0;
}
时间复杂度 :
最好的情况下就是O(n),本身就是有序的,比较n-1次就可以。
当最坏的情况排序表是逆序的,比如{6,5,4,3,2]。因此,我们得出直接插入排序法的时间复杂度为O(n²)。同样的O(n²)时间复杂度,直接插入排序法比冒泡和简单选择排序的性能要好一些。
4.计数排序(桶排序)
计数排序运用的是桶思想,通过统计每个元素出现的次数,并计算元素在排序后数组中的位置,实现对整数数组的排序。
- 首先遍历数组,找到最大值max,设有max+1个桶。
- 将数组中的每一个元素对应一个桶,找到对应的桶号,对桶的计数进行++操作。
- 遍历桶数组,看对应的桶内计数为几就取出几个下标值,放到原数组。
计数排序就像统计人数身高一样,首先找到最高的,然后遍历学生身高数据,统计每个身高值出现的次数。将每个身高值作为索引,将对应的身高数组的值加1。
#include <iostream>
#include <vector>using namespace std;int Maxval(int* arr, int n) {int maxVal = arr[0];for (int i = 1; i < n; i++) {if (arr[i] > maxVal) {maxVal = arr[i];}}return maxVal;
}void BucketSort(int* arr, int n) {int maxVal = Maxval(arr, n);int* bucket = new int[maxVal + 1]();for (int i = 0; i < n; i++) {bucket[arr[i]]++;}int index = 0;for (int i = 0; i <= maxVal; i++) {while (bucket[i] > 0) {arr[index++] = i;bucket[i]--;}}delete[] bucket;
}int main() {int arr[] = {4, 2, 6, 5, 8, 9};int n = sizeof(arr) / sizeof(int);for (int i = 0; i < n; i++) {cout << arr[i] << " ";}cout << endl;BucketSort(arr, n);for (int i = 0; i < n; i++) {cout << arr[i] << " ";}cout << endl;return 0;
}
时间复杂度:
在分析复杂度时要小心循环,并不是循环嵌套就是相乘的关系。里层的while循环是对桶中的元素个数进行判断的,有元素就会进入循环,而我们的元素总共有n个也就是只会进入n次循环。因此桶排序的时间复杂度为O(N)。
5.堆排序
堆排序是一种基于二叉堆数据结构的排序算法。它的主要思想是通过构建最大堆(或最小堆)来进行排序。
第一步:
我们将待排序数组形象成一个堆结构,数组从左到右插入树中,并将其调整为最大堆(堆结构:左节点的下标是2i+1,右节点下标2i+2)
初始化堆(将数组调整成最大堆)从最后一个父节点开始调整(即i = n/2-1)。
用黄色填充的是将要交换的节点
比较前一个父节点调整最大堆直至父节点下标为0。
这时5还是小于7的,所以应该再次交换位置
这样就完成了最大堆的创建(最大堆的特点:任何一个父节点的值都大于其子节点的值)
第二步:
将堆顶元素与待排序数组(假设待排序的数据数量为arr)最后一个元素进行交换,swap(&a[0], &a[nums-1]);
这样保证了最后一位是最大值
第三步:
待排序的数据量减少一个,即arr--;将待排序数组重新调整成最大堆结构,重复第二步,循环n-1次,将所有数据排序完成。
当堆顶元素和末尾元素交换后8也不再进行排序 ,直至全部排序结束
#include <iostream>
#include <vector>using namespace std;// 下沉操作
void heapify(vector<int>& arr, int n, int i) {int largest = i; // 当前节点的索引int left = 2 * i + 1; // 左子节点的索引int right = 2 * i + 2; // 右子节点的索引// 找出当前节点、左子节点和右子节点中的最大值if (left < n && arr[left] > arr[largest]) {largest = left;}if (right < n && arr[right] > arr[largest]) {largest = right;}// 如果最大值不是当前节点,则交换它们,并继续向下调整堆if (largest != i) {swap(arr[i], arr[largest]);heapify(arr, n, largest);}
}// 堆排序
void heapSort(vector<int>& arr) {int n = arr.size();// 构建最大堆for (int i = n / 2 - 1; i >= 0; i--) {heapify(arr, n, i);}// 从堆中取出元素,依次放置在正确的位置上for (int i = n - 1; i >= 0; i--) {swap(arr[0], arr[i]); // 将堆顶元素与当前最后一个元素交换heapify(arr, i, 0); // 重新调整堆}
}int main() {vector<int> arr = {9, 7, 2, 1, 5, 8, 6};for (int num : arr) {cout << num << " ";}cout << endl;heapSort(arr);for (int num : arr) {cout << num << " ";}cout << endl;return 0;
}
时间复杂度:
堆排序的时间复杂度为O(nlog.n)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlog-n)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n²)的时间复杂度了。
更多推荐
C++ 排序算法的图文讲解
发布评论