排序算法 | 平均时间复杂度 | 最好情况 | 最差情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
一、快速排序
快速排序的基本思想可以简单的分为三步:1.找到一个基准 2.遍历容器将比基准小的元素放在基准的左边,比基准大的元素全部放在基准的右边 3.以左边部分作为新的容器进行1.2两步操作,以右边部分作为新的容器进行1.2两步操作。
//写法一
void quicksort(std::vector<int> &nums, int left, int right){
if(left >= right) return; //递归终止条件
int pivot = partition(nums, left, right); //partition返回分界点下标
quicksort(nums, left, pivot-1);
quicksort(nums, pivot+1, right);
}
int partition(vector<int> &nums, int left, int right){
int start = left;
while(left < right){
while(left < right && nums[start] < nums[right]) right--;
while(left < right && nums[start] >= nums[left]) left++;
swap(nums[left], nums[right]);
}
swap(nums[left], nums[start]);
return left;
}
//写法二
void quickSort(vector<int>& nums, int left, int right)
{
if (left >= right) return;
int prev = left;
int cur = left + 1;
int pivot = nums[left];
while (cur <= right){
if (nums[cur] < pivot && ++prev != cur)
swap(nums[prev], nums[cur]);
++cur;
}
swap(nums[left], nums[prev]);
quickSort(nums, left, prev - 1);
quickSort(nums, prev + 1, right);
}
二、归并排序
归并排序利用了分治的思想对容器内的元素进行排序,对于一个序列,首先将其拆分为两个长度差不超过1的子序列。当全部拆分完毕后,采用递归的合并方法,有序的将子序列合并使得整个序列有序。
vector<int> tmp;
void mergeSort(vector<int>& nums, int left, int right) {
if (left >= right) return;
int mid = (left + right) >> 1;
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
int i = left, j = mid + 1;
int cnt = 0;
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) tmp[cnt++] = nums[i++];
else tmp[cnt++] = nums[j++];
}
while (i <= mid) tmp[cnt++] = nums[i++];
while (j <= right) tmp[cnt++] = nums[j++];
for (int i = 0; i < right - left + 1; ++i) {
nums[i+left] = tmp[i];
}
}
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size(), 0);
mergeSort(nums, 0, nums.size() - 1);
return nums;
}
三、堆排序
堆排序就是将序列建成大根堆,再将堆顶元素(最大值)与末尾元素交换,再调整剩下(除末尾元素)的堆为大根堆,重复上述步骤。
void maxHeapify(vector<int>& nums, int i, int len) {
for (; (i << 1) + 1 <= len;) {
int lson = (i << 1) + 1;
int rson = (i << 1) + 2;
int large;
if (lson <= len && nums[lson] > nums[i]) {
large = lson;
} else large = i;
if (rson <= len && nums[rson] > nums[large]) {
large = rson;
}
if (large != i) {
swap(nums[i], nums[large]);
i = large;
} else break;
}
//建大根堆
void buildMaxHeap(vector<int>& nums, int len) {
for (int i = len / 2; i >= 0; --i) {
maxHeapify(nums, i, len);
}
}
//堆排序
void heapSort(vector<int>& nums) {
int len = (int)nums.size() - 1;
buildMaxHeap(nums, len);
for (int i = len; i >= 1; --i) {
swap(nums[i], nums[0]);
len -= 1;
maxHeapify(nums, 0, len);
}
}
为什么多用快排而非堆排、归并
相较于快排和堆排,归并有O(n)的空间复杂度。
相较于快排,堆排有两大缺陷:1.访问数据并非顺序访问 2.排序过程中交换次数过多。