冒泡排序
//冒泡 通过多次交换相邻元素将较大(或较小)的元素逐渐 "浮" 到数组的一端。该算法的时间复杂度为 O(N^2)。
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],arr[j+1]);
}
}
}
}
两层循环,一次次比较交换。
选择排序
//选择排序 在每次迭代中选择未排序部分的最小(或最大)元素,并将其放在已排序部分的末尾。时间复杂度为 O(N^2)。
void selectionSort(std::vector<int>& arr) {
int n = arr.size();
for(int i=0; i<n; i++) {
int minIndex=i;
for(int j=i+1; j<n; j++) { //找未排序部分的最小值对应的index
if(arr[j]<arr[minIndex]) {
minIndex=j;
}
}
//找到未排序部分的最小值之后,需要放在有序部分的末尾,也就是i位置.所以交换.
swap(arr[i],arr[minIndex]);
}
}
在未排序部分选一个最小的放到有序部分末尾。
插入排序
//插入排序 将未排序部分的元素逐个插入到已排序部分的正确位置。时间复杂度为 O(N^2),但对于小型数据集或部分已排序的数据,插入排序具有较好的性能。
void insertionSort(std::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;
}
}
从未排序部分取一个插入到有序部分。
快速排序
//快速排序(Quick Sort):也是采用分治策略的排序算法,通过选择一个基准值,将数据分为小于基准值和大于基准值的两部分,然后递归地对这两部分进行排序。时间复杂度为 O(NlogN),具有较好的性能。
// 将数组以升序进行排列!
// 分区操作函数,将数组分成两个部分并返回基准元素的最终位置
void quickSort(vector<int> &nums, int l, int r) {
if (l + 1 >= r) {
return;
}
int first = l, last = r - 1, key = nums[first];
while (first < last) {
while(first < last && nums[last] > key) {
--last;
}//从右往左找小于基枢的值
nums[first] = nums[last];
//把小于基枢的值赋给左边.
while (first < last && nums[first] < key) {
++first;
}//从左往右找大于基枢的值
nums[last] = nums[first]; //找到之后把这个大于基枢的值再赋给右边.
}//这样循环下去,first指向的位置最终左边都是小于基枢,右边都是大于基枢. first=last把基枢位置确定下来.
nums[first] = key;
quickSort(nums, l, first);
quickSort(nums, first + 1, r);
}
分支法,先分区,找到分区的位置后,两边继续
quickSort(nums, l, first);
quickSort(nums, first + 1, r);
首先设置第一个为基枢。从右往左找比基枢小的值,然后复制到first,接着从first向右找第一个比基枢大的值,复制到fast位置,然后fast往左找小的,再给first赋值,直到first和last相等,此次,基枢位置确定,把最开始设置的基枢赋值过来。基枢位置确定之后,就有了左右分区。在左右分区继续快排。
归并排序
//归并排序
void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) {
if (l + 1 >= r) {
return;
}
// divide
int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
// conquer
int p = l, q = m, i = l;
while (p < m || q < r) {
if (q >= r || (p < m && nums[p] <= nums[q])) {
temp[i++] = nums[p++];
} else {
temp[i++] = nums[q++];
}
}
for (i = l; i < r; ++i) {
nums[i] = temp[i];
}
}
在合并过程中,我们使用双指针 p
和 q
分别指向左右两个子数组的起始位置,然后比较指针位置处的元素大小,将较小的元素放入临时数组 temp
中。最后,将临时数组中的元素复制回原数组。
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 3};
// std::sort(numbers.begin(), numbers.end());
//快排 std::sort:使用快速排序(Quick Sort)实现的排序算法,平均时间复杂度为 O(NlogN)
// std::stable_sort(numbers.begin(), numbers.end());
//std::stable_sort:使用归并排序(Merge Sort)实现的排序算法,保持相等元素的相对顺序。时间复杂度为 O(NlogN)
// bubbleSort(numbers);//冒泡排序的平均时间复杂度为 O(N^2)
// selectionSort(numbers);//选择排序的时间复杂度为 O(N^2)
// insertionSort(numbers);//插入排序的时间复杂度为 O(N^2)
// quickSort(numbers,0,numbers.size());
vector<int> temp(numbers.size());
merge_sort(numbers, 0, numbers.size(), temp);
std::cout << "Sorted numbers: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
是否稳定
-
稳定的排序算法:
- 冒泡排序(Bubble Sort)
- 插入排序(Insertion Sort)
- 归并排序(Merge Sort)
- 基数排序(Radix Sort)
-
不稳定的排序算法:
- 选择排序(Selection Sort)
- 快速排序(Quick Sort)
- 堆排序(Heap Sort)
- 希尔排序(Shell Sort)
时间复杂度
- 冒泡排序、插入排序、选择排序的时间复杂度为 O(n^2)。
- 归并排序、快速排序、堆排序的平均时间复杂度为 O(n log n)。
- 基数排序的时间复杂度为 O(d * (n + k))
空间复杂度
空间复杂度是指算法在运行过程中所需要的额外的存储空间大小。以下是几种常见排序算法的空间复杂度:
-
冒泡排序(Bubble Sort):空间复杂度为 O(1),因为只需要常数级别的额外空间用于交换元素。
-
插入排序(Insertion Sort):空间复杂度为 O(1),因为只需要常数级别的额外空间用于交换元素。
-
选择排序(Selection Sort):空间复杂度为 O(1),因为只需要常数级别的额外空间用于交换元素。
-
归并排序(Merge Sort):归并排序是一种分治算法,在归并过程中需要额外的空间来存储临时数组,所以空间复杂度为 O(n)。
-
快速排序(Quick Sort):快速排序是一种原地排序算法,通常不需要额外的空间,但是如果使用递归实现,则需要系统栈来存储递归调用的上下文,因此空间复杂度在最坏情况下可能达到 O(n),平均情况下为 O(log n)。
-
堆排序(Heap Sort):堆排序是一种原地排序算法,不需要额外的空间,因此空间复杂度为 O(1)。
-
希尔排序(Shell Sort):希尔排序是一种改进的插入排序算法,空间复杂度取决于间隔序列的选择,一般为 O(1) 到 O(n)。
-
基数排序(Radix Sort):基数排序需要额外的空间来存储桶,空间复杂度取决于数据的范围和位数,一般为 O(n + k),其中 n 是数组长度,k 是数据的范围。