(主要为了准备面试,把排序算法再总结实现一下)
图片引用自https://www.cnblogs.com/onepixel/articles/7674659.html,侵删
排序算法
比较类排序
通过比较来决定元素间的相对次序,时间复杂度不能突破O(nlogn)。
一、交换排序
1、冒泡排序
- 比较相邻元素,若第一个比第二个大,则交换位置。
- 对每一队相邻元素作相同的工作,直至当前最大元素放在当前最靠后的位置
- 重复1,2步骤,直至排序完成(第二小的元素被放到第二个位置)
//冒泡排序
//一次冒泡,对nums的前n个元素进行一次冒泡操作,将当前最大值放在最后面,即nums[n-1]的位置
void Bubble(vector<int>& nums,int n)
{
for(int i=0;i<n-1;i++)
{
if(nums[i]>nums[i+1])
{
int temp = nums[i];
nums[i] = nums[i+1];
nums[i+1] = temp;
}
}
}
//先进行n = l的一次冒泡,直至n = 2的冒泡
void BubbleSort(vector<int>& nums) {
int l = nums.size();
for(int i=l;i>1;i--)
{
Bubble(nums,i);
}
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
BubbleSort(nums);
return nums;
}
2、快速排序
算法描述:
- 从序列中选择一个轴点元素(位置0)
- 利用轴点将序列分割成两个子序列
将小于轴点的元素放在轴点前面,大于轴点的元素放在轴点后面,等于轴点的元素两边都可以 - 对子序列进行1,2操作,直至子序列只剩下一个元素
本质:将每个元素都转换为轴点元素
复杂度分析:
若轴点元素左右分布极不均衡,则时间复杂度为O(N^2),为了防止这种情况,随机选取轴点
//快速排序
//对[begin,end)范围进行快速排序
void QuickSort(vector<int>& nums,int begin,int end){
if(end-begin<2)
return ;
//确定轴点位置,并进行nums的重建操作
int mid = pivotIndex(nums,begin,end);
//对子序列进行快速排序
QuickSort(nums,begin,mid);
QuickSort(nums,mid+1,end);
}
//返回轴点元素的最终位置,并将大于pivot的元素放在右边,小于pivot的元素放在左边。
int pivotIndex(vector<int>& nums,int begin,int end){
int pivot = nums[begin];
//end指向最后一个元素
end--;
//reverse表示是否逆序(从后往前)
//先从后往前遍历,若nums[end]<pivot,则需要将其移到begin处,并将begin后移一位,然后就该从前往后开始遍历。若nums[end]>=pivot,不需要移动end处元素,并将end前移一位,继续从后往前遍历。
//从前往后遍历时,若nums[begin]>=pivot,则需要将其移到end处,并将end前移一位,然后就该从后往前开始遍历。若nums[begin]<pivot,不需要移动begin处元素,并将begin后移一位,继续从前往后遍历。
//这样操作是将等于pivot的元素移动到pivot右侧,也可以移到左侧。
bool reverse = true;
//将nums[begin:end]的元素大于pivot的放在右侧,小于pivot的放在左侧
while(begin<end)
{
if(reverse)
{
if(nums[end]<pivot)
{
nums[begin] = nums[end];
begin++;
reverse = false;
}
else
end--;
}
else
{
if(nums[begin]>=pivot)
{
nums[end] = nums[begin];
end--;
reverse = true;
}
else
begin++;
}
}
//begin=end时,指向的就是pivot最终的位置
nums[begin] = pivot;
return begin;
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
QuickSort(nums,0,nums.size());
return nums;
}
二、插入排序
1、简单插入排序
算法描述
- 从第二个元素开始,因为第一个元素可以认为已经被排序;
- 取出当前元素v,在已经排序的元素序列中从后向前扫描;
- 如果当前元素v小于前面元素(已排序),将前面元素后移一个位置;
- 重复步骤3,直到找到已排序的元素小于或者等于v的位置;
- 将新元素插入到该位置后面;
- 重复步骤2~5。
版本一(常规)
//插入排序
//一次插入,将nums[n]个元素插入已经排好序的前n个元素中
void insert(vector<int>& nums,int n){
int i = n;
int key = nums[n];
//注意i>0的条件
while(i>0 && nums[i-1]>key)
{
nums[i] = nums[i-1];
i--;
}
nums[i] = key;
}
//从nums[1]-nums[l-1]进行一次插入
void InsertSort(vector<int>& nums){
int l = nums.size();
for(int i=1;i<l;i++)
insert(nums,i);
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
InsertSort(nums);
return nums;
}
版本二(利用二分查找进行优化)
由于需要找到插入的位置,前面的数组已经是有序的,可以利用二分查找代替常规版本的线性查找
//二分查找:找到第一个比nums[idx_key]大的元素索引,这个索引即为nums[idx_key]需要插入的位置。这样操作会使得等于nums[idx_key]的值在nums[idx_key]左侧
int BinarySearch(vector<int>& nums,int idx_key){
//找到比x大的第一个元素索引
//在[begin,end)区间查找
int begin = 0;
int end = idx_key;
int key = nums[idx_key];
while(begin<end)
{
int mid = (begin+end)>>1;
if(nums[mid]<=key)
{
//不取<=key的索引值,因此begin = mid+1,跳过mid这个位置
begin=mid+1;
}
else
end = mid;
}
return begin;
}
//将idx及其之后的元素后移,并将nums[idx_key]插入nums[idx]的位置
void insert_bs(vector<int>& nums,int idx_key,int idx){
//idx为x需要插入的位置索引
int key = nums[idx_key];
for(int i=idx_key;i>idx;i--)
nums[i] = nums[i-1];
nums[idx] = key;
}
//从第二个元素到最后一个元素执行一次插入
vector<int> InsertSort_bs(vector<int>& nums) {
for(int i=1;i<nums.size();i++)
{
int idx = BinarySearch(nums,i);
//cout<<nums[i]<<idx<<endl;
insert_bs(nums,i,idx);
}
return nums;
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
InsertSort_bs(nums);
return nums;
2、希尔排序
算法描述
- 先给定一个步长序列,或者生成一个步长序列:[数组长度/2,数组长度/2^2,…,1]
- 若有k个步长,则对序列进行k趟排序
- 每次排序选取第i个步长step(从大到小),将数组分为step个子序列,对这step个子序列使用插入排序。直至step=1进行排序后得到整个有序的数组。
//希尔排序
//生成步长序列
vector<int> generateStepSequence(int l)
{
vector<int> stepsequence;
l = l>>1;
while(l>0){
stepsequence.push_back(l);
//cout<<l;
l = l>>1;
}
return stepsequence;
}
//对某个step进行一次排序
void StepSort(vector<int>& nums,int step)
{
//对step列依次进行排序
for(int col=0;col<step;col++)
{
//对每列进行插入排序
//每列元素索引为:col,col+step,col+step*2,...
//从第二个元素开始插入
for(int begin = col+step;begin<nums.size();begin+=step)
{
int cur = begin;
int val = nums[begin];
while(cur>col && val<nums[cur-step])
{
nums[cur] = nums[cur-step];
cur-=step;
}
nums[cur] = val;
}
}
}
//希尔排序:对每个step进行一次stepsort
void ShellSort(vector<int>& nums)
{
vector<int> stepsequence = generateStepSequence(nums.size());
for(int i=0;i<stepsequence.size();i++)
StepSort(nums,stepsequence[i]);
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
ShellSort(nums);
return nums;
}
三、选择排序
1、简单选择排序
每一轮在未排序数组中选择最大值放到最后。直至所有元素排序完毕。
算法描述
- 从数组的前n个元素中查找最大值,并与第n+1个元素交换,即将最大值移到当前最末尾位置。
- 第一趟排序,将整个数组的最大值放在最后,有序数组的长度加一,无序数组长度减一;
- 继续对无序数组进行一次排序;直至无序数组长度为1
//选择排序
void swap(vector<int>& nums,int i1,int i2){
int temp = nums[i1];
nums[i1] = nums[i2];
nums[i2] = temp;
}
//找到前n个未排序数组中最大元素的索引值
int findMax(vector<int>& nums,int n){
int idx = 0;
for(int i=0;i<n;i++)
{
if(nums[idx]<nums[i])
idx = i;
}
return idx;
}
//从l(数组长度)长度的数组开始(整个数组),找到最大值,与最后一个元素交换位置,再对前l-1长度的数组重复操作,直至剩余数组长度为1
void SelectSort(vector<int>& nums) {
int l = nums.size();
for(int i=l;i>=1;i--)
{
int max_idx = findMax(nums,i);
swap(nums,max_idx,i-1);
}
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
SelectSort(nums);
return nums;
}
2、堆排序
对于选择排序算法的优化,由于选择排序每次需要选择当前的最大元素进行交换。简单选择排序是通过线性遍历找到最大值位置,堆排序是利用堆的性质查找最大值,将O(N)的查找时间复杂度降低为O(logN)。
- 原地建堆build_heap
- 重复以下操作,直至堆的元素数量为1:
a)将首个元素(堆顶->最大值)与末尾元素交换
b)堆的元素数量减1
c)对0号元素进行Heapify操作
void swap(vector<int>& nums,int i1,int i2){
int temp = nums[i1];
nums[i1] = nums[i2];
nums[i2] = temp;
}
void build_heap(vector<int>& nums){
//build_heap分为自上而下和自下而上,自下而上时间复杂度较小O(N),自上而下为O(NlogN)
//自下而上是从最后一个非叶子节点开始进行heapify操作,即将当前堆调整为大根堆
int last_node = nums.size()-1;
int parent = (last_node-1)/2;
for(int i=parent;i>=0;i--)
{
Heapify(nums,nums.size(),i);
}
}
//对于除堆顶元素外,左右子堆都符合大根堆特性的堆进行调整,即将堆顶元素取走后的恢复操作
//当前堆有n个元素,需要调整索引为idx的元素
void Heapify(vector<int>& nums,int n,int idx){
if(idx>=n)
return ;
//堆顶元素的左右孩子索引
int c1 = idx*2+1;
int c2 = idx*2+2;
int max = idx;
//找到堆顶,左孩子,右孩子中最大的节点索引
if(c1<n && nums[c1]>nums[max])
max = c1;
if(c2<n && nums[c2]>nums[max])
max = c2;
//如果最大值不为堆顶元素,则需要进行调整
if(max!=idx)
{
//将最大索引元素与堆顶交换,并对交换的一边子堆进行heapify操作
swap(nums,idx,max);
Heapify(nums,n,max);
}
//return nums;
}
void HeapSort(vector<int>& nums) {
int heapsize = nums.size();
//原地建堆
build_heap(nums);
while(heapsize>1)
{
//将最大值(第一个元素)与当前堆最后一个元素进行交换
swap(nums,0,heapsize-1);
//无序堆的长度减1
heapsize--;
//恢复堆的性质(对索引0进行恢复堆)
Heapify(nums,heapsize,0);
}
//函数调用
vector<int> sortArray(vector<int>& nums) {
HeapSort(nums);
return nums;
}
四、归并排序
算法描述
- 不断将当前序列平均分割成两个子序列(直至每个序列中只有一个元素)
- 不断将两个子序列合并为一个有序序列(直至只剩下一个有序序列)
//[begin,mid) [mid,end)两个区间进行有序合并
//原地合并,需要备份一个区间的数组,然后设置三个指针分别指向两个子数组和原数组
void merge(vector<int>& nums,int begin,int mid,int end){
//mid为左边第一个元素,end为最后一个元素索引+1
int left_size = mid-begin;
int right_size = end -mid;
int left[left_size];
cout<<left_size<<" "<<right_size<<endl;
//备份左边数组
for(int i=0;i<left_size;i++)
left[i] = nums[begin+i];
//merge
int li = 0,le = mid-begin;
int ri = mid,re = end;
//整个数组的指针(原地merge)
int ai = begin;
while(li<le && ri<re)
{
if(left[li]<nums[ri])
{
nums[ai] = left[li];
li++;
ai++;
}
else
{
nums[ai] = nums[ri];
ri++;
ai++;
}
}
//若右边数组填充完,则将左边数组依次填充进去
while(li<le)
{
nums[ai] = left[li];
li++;
ai++;
}
//若左边数组填充完,不需要进行操作,右边数组是有序排列在nums中的
}
//对[L,R)的区间进行归并排序
void mergeSort(vector<int>& nums,int L,int R){
int M = (L+R)>>1;
//当M=L或M=L时,表示需要merge的元素数量为0个,结束递归
if(M-L<1 || R-M<1)
return ;
else
{
//divide
mergeSort(nums,L,M);
mergeSort(nums,M,R);
//merge!!!重点:将[L,M) [M,R)两个区间进行有序合并
merge(nums,L,M,R);
}
//return nums;
}
非比较类排序
不通过比较来决定元素间的相对次序,可以突破比较排序的时间复杂度。(这里不介绍了)