参考文章:堆排序1、堆排序二
前置知识:
(i-1)/2
,第i个节点的左儿子:i*2+1
,第i个节点的右儿子:i*2+2
,这里i从0开始;n
个,则最后一个有儿子的节点(n-1-1)/2=n/2-1
堆排序基本思想:分为建堆
和排序
两步,而核心步骤是对堆的属性进行维护
复杂度:
O(nlogn)
O(1)
代码实现:实列测试
class Solution {
public:
void heapify(vector<int>& nums,int n,int i) //维护第i个节点的堆结构
{
int largest = i;
int left = i*2+1;
//最多遍历树的高度,时间复杂度O(log n)
while(left<n) //当left是最后一个节点,也还需要判断一次
{
if(left<n&&nums[largest]<nums[left]) largest = left;
if(left+1<n&&nums[largest]<nums[left+1]) largest = left+1; //
if(largest==i) break; //当没有子节点的时候,或者不需要维护的时候跳出while
swap(nums[largest],nums[i]); //将大的值放到i的位置上
i = largest; //向下循环子节点
left = i*2+1; //子节点的左儿子
}
}
void heapSort(vector<int>& nums,int n)
{
//从底向上建堆时间复杂度O(n),从顶向下建队时间复杂度是O(n);
for(int i =n/2-1;i>=0;i--) {
heapify(nums,n,i);
}
for(int i=n-1;i>=0;i--) //堆排序 时间复杂度O(nlogn)
{ //每次将最后一个节点和第一个节点互换
swap(nums[i],nums[0]);
heapify(nums,i,0); //进行枝剪维护第0个节点
}
}
vector<int> sortArray(vector<int>& nums) {
heapSort(nums,nums.size());
return nums;
}
};
O(nlogn)
,最坏时间复杂度O(n^2)
-序列基本有序,递归O(n)
次O(log n)
二路快排,普通快排
class Solution {
public:
int partition(vector<int>& nums,int l,int r)
{
int pivot = rand()%(r-l+1)+l;
swap(nums[pivot],nums[r]);
int i=l;
for(int j=l;j<=r-1;j++) //用j遍历数组,
{
if(nums[j]<nums[r])
{
swap(nums[j],nums[i]); //指针i始终指向pivot右边的第一个元素
i++; //i前面的都是小于pivot的元素,
}
}
//遍历完了,i依旧指向大于pivot的元素的第一位
// j指向r的位置,也就是pivot的位置,只需要将pivot的位置和i的位置进行交换即可
swap(nums[r],nums[i]);
return i;
}
void quikSort(vector<int>& nums,int left,int right)
{
if(left>=right) return;
int mid = partition(nums,left,right);
quikSort(nums,left,mid-1);
quikSort(nums,mid+1,right);
}
vector<int> sortArray(vector<int>& nums) {
quikSort(nums,0,nums.size()-1);
return nums;
}
};
三路快排,用于重复元素多的情况
class Solution {
public:
vector<int> partition(vector<int>& nums,int l,int r)
{
int pivot = rand()%(r-l+1)+l;
swap(nums[pivot],nums[r]);
// 三路快排,p指向pivot元素相同的第一个元素
// i指向大于pivot的元素的第一个位置
int i=l;
int p=l;
for(int j=l;j<=r-1;j++)
{
if(nums[j]<nums[r])
{
swap(nums[j],nums[i]);
swap(nums[i],nums[p]);
i++;
p++;
}
else if(nums[j]==nums[r])
{
swap(nums[j],nums[i]);
i++;
}
}
swap(nums[r],nums[i]);
return vector<int>{p,i};
}
void quikSort(vector<int>& nums,int left,int right)
{
if(left>=right) return;
vector<int> v = partition(nums,left,right);
quikSort(nums,left,v[0]-1);
quikSort(nums,v[1]+1,right);
}
vector<int> sortArray(vector<int>& nums) {
quikSort(nums,0,nums.size()-1);
return nums;
}
};
O(nlogn)
O(n)
,如果是给链表排序则只需要O(1)
class Solution {
public:
void merge(vector<int>&nums,vector<int>& arr,int left,int mid,int right)
{
int l = left;//左边第一个未排序元素
int r = mid+1;//右边第一个未排序元素
int pos = left;//arr数组
// 合并
while(l<=mid&&r<=right)
{
if(nums[l]<nums[r]) arr[pos++] = nums[l++];
else arr[pos++] = nums[r++];
}
//合并剩余元素
while(l<=mid) arr[pos++] = nums[l++];
while(r<=right) arr[pos++] = nums[r++];
//将临时数组进行拷贝
while(left<=right)
{
nums[left] = arr[left];
left++;
}
}
void mergeSort(vector<int>& nums,vector<int>& arr,int left,int right)
{
if(left>=right) return;
int mid = (right+left)/2;
mergeSort(nums,arr,left,mid);
mergeSort(nums,arr,mid+1,right);
merge(nums,arr,left,mid,right);
}
vector<int> sortArray(vector<int>& nums) {
vector<int> arr(nums.size());
mergeSort(nums,arr,0,nums.size()-1);
return nums;
}
};
冒泡排序基本思想:每次循环将最大元素交换到最右边,每次内部循环都需要交换
和选择排序的区别:异曲同工,选择排序交换次数少,但是在数组有序的情况下,依旧需要完整的遍历完,时间复杂度稳定到O(n^2)
,冒泡排序,可以在数组有序的情况下提前结束
复杂度:
O(n^2)
,最好时间复杂度O(n)
,平均时间复杂度O(n^2)
- 空间复杂度:O(1)
代码实现:
// 简单版本,和选择排序基本一致
class Solution {
public:
void bubbleSort(vector<int>& nums,int n)
{
for(int i=nums.size()-1;i>=0;i--)
{
for(int j=0;j<i;++j)
{
if(nums[j]>nums[j+1]) swap(nums[j],nums[j+1]);
}
}
}
vector<int> sortArray(vector<int>& nums) {
bubbleSort(nums,nums.size());
return nums;
}
};
// 优化版本
class Solution {
public:
void bubbleSort(vector<int>& nums,int n)
{
bool flag = false;
for(int i=nums.size()-1;i>=0;i--)
{
flag = false;
for(int j=0;j<i;++j) //一般过去是有序的,则时间复杂度只有O(n)
{
if(nums[j]>nums[j+1])
{
swap(nums[j],nums[j+1]);
flag = true;
}
}
if(!flag) break;
}
}
vector<int> sortArray(vector<int>& nums) {
bubbleSort(nums,nums.size());
return nums;
}
};
选择排序基本思想:每次选择一个最大的元素排到后面,每次内部循环只需要交换一次
复杂度:
代码实现:
class Solution {
public:
void selectSort(vector<int>& nums,int n)
{
int Max=-1;
for(int i=nums.size()-1;i>=0;i--)
{
Max = i;
for(int j=0;j<i;++j)
{
if(nums[j]>nums[Max]) Max=j;
}
swap(nums[i],nums[Max]);
}
}
vector<int> sortArray(vector<int>& nums) {
selectSort(nums,nums.size());
return nums;
}
};
插入排序基本思想:每次将一个元素插入到前面维护好顺序的元素里面
复杂度:
O(n^2)
,最好为O(n)-基本有序的情况代码实现:
class Solution {
public:
void InsertSort(vector<int>& nums,int n)
{
for(int i=1;i<n;++i)
{
// 每次将第i个元素插入到前i-1数组中
int tmp = nums[i];
int j=i;
while(j>0 && nums[j-1]>tmp)
{
nums[j] = nums[j-1];
--j;
}
nums[j] = tmp;
}
}
vector<int> sortArray(vector<int>& nums) {
InsertSort(nums,nums.size());
return nums;
}
};
基本思想:前提是数组有序,通过中间点折半的思想实现快速的查找
红蓝边界思想:
int left = -1,right = N; // 数组下标从0到N-1
while left+1!=right
m = (left+right)/2;
if IsBlue(m)
left = m;
else
right = m;
return left or right; //返回值根据题目要求来定
复杂度:
O(log n)
代码实现:
在有序数组中查找元素的下标:测试实列
class Solution {
public:
/*红蓝边界求值,时间复杂度O(log n)
1.没有这个元素的话,且right进行了移动
2.没有这个元素的话,且right没有进行移动
3.有这个元素,直接返回right
*/
int search(vector<int>& nums, int target) {
int left = -1;
int right = nums.size();
while(left+1!=right)
{
int mid = (left+right)/2;
if(nums[mid]<target) left = mid;
else right = mid;
}
//需要判断对应的节点不存在的可能
if(right==nums.size()||nums[right]!=target) return -1;
else return right;
}
};