排序算法分类:
比较排序,:冒泡排序,鸡尾酒排序,选择排序,插入排序,归并排序,堆排序,快速排序,希尔排序。
非比较排序:计数排序,基数排序,桶排序等。
排序算法稳定性:
如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,就是稳定的。
排序算法复杂度:
排序算法 | 最坏时间复杂度 | 最好时间复杂度 | 平均时间复杂度 | 空间复杂度 | 是否稳定 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(nlogn) | O(n^2) | O(1) | 不稳定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(n^2) | O(nlogn) | O(nlogn) | O(logn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
希尔排序 | O(n^2) | O(n^1.3) | O(nlogn) | O(1) | 不稳定 |
桶排序 | O(n^2) | O(n^2) | O(n+k) | O(n+k) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
基数排序 | O(N∗M) | O(N∗M) | O(N∗M) | O(M) | 稳定 |
希尔排序的时间复杂度是:O(nlogn)~O(n ^ 2 ) ,最好情况是O(n ^1.3), 与序列选择有关。
简单描述:有时间在补充例子吧,来自自己的弱智笔记…
1.冒泡排序:
从头到尾依次比较相邻两个元素,Ai和Aj,如果Ai大于Aj,交换两个元素,所以每次都有一个元素放到最终位置。遍历一轮以后再从第一个元素开始,可以设置一个标志,如果某轮次没有交换,则停止,表明排序结束。
2.鸡尾酒排序:
其实和冒泡排序思想一样,只不过每次交换遍历方向,先从左往右找大的交换最后放到最右,然后从右往左返回,找最小的交换,一直到最左。
2.简单选择排序:
每次从其余元素的头到尾遍历寻找一个最小的元素放到最前。也是每次遍历都有一个元素放到最终位置。
3.直接插入排序
从左往右每次用当前元素和前面所有元素依次比较,如果前一个更大,就把它后移,一直到小于等于它的时候放入那个位置。
4.折半插入排序
直接插入排序的时候,用折半查找的方法找插入位置。
5.希尔排序:
就是分组直接插入,比如8个元素,设置一个间隔8/2=4,每隔4个元素一组,然后每隔4/2=2个元素一组,每次都是进行直接插入排序
6.快速排序:
基于分治的思想,设置两个指针,找一个元素(比如最后一个)作为中间元素,然后从头找大于这个元素的交换到最右,然后在从后往前找小于这个元素的交换到左面,最后位置放入这个中间元素,中间元素位置就是它的最终位置,左面元素都小于它,右面元素都大于它。然后左面和后面元素再分别重复刚才步骤。
7.归并排序:
把数字分组,每组排序再合并,继续排序。比如8个元素,先分成4组,每组排序,在合并为2组排序,最后合并在一起排序。
8.计数排序:
当输入的元素是 n 个 0到 k之间的整数时,可以用计数排序。计数排序不是基于比较的,排序的速度快于任何比较排序算法。
思想是,先统计数组里最大最小值,然后统计每个元素数目存入hash数组,然hash数组每个元素等于前一个元素加当前元素,其实就是求该元素的真实位置。然后创建新数组填充元素,某个元素填充一次后要ct–.
以上做法有一些问题:空间复杂度大,排序不稳定。
步骤:
1)遍历找出待排序的数组中最大和最小的元素(max和min),创建hash数据大小为max-min+1(解决空间复杂度问题);
2)统计每个元素出现的次数,数目存入hash数组的,(注意位置,如果大小为max-min+1需要换算);
3.对所有的计数累加(从hash中的第一个元素开始,每一项和前一项相加),这样可以统计实际位置,比如0出现5次,1出现2次,此时hash[0]=5, hash[1]=2,更新为hash[0]=5,hash[1]=7; (保证稳定性)
4.反向填充目标数组(保存排序完元素的数组):将每个元素i放在新数组的第hash[i]项,每放一个 元素就将hash[i]减去1 ; (保证稳定性)
计数排序的时间复杂度为 O(N+K)。因为算法过程中需要申请一个额外空间和一个与待排序集合大小相同的已排序空间,所以空间复杂度为 O(N+K)。由此可知,计数排序只适用于元素值较为集中的情况,若集合中存在最大最小元素值相差甚远的情况,则计数排序开销较大、性能较差。
9.桶排序
数据分布比较均匀的时候可以用,比如范围0到n的k个数字,把数字划分成k/n个部分,每部分是一个桶,数字映射到自己属于的范围的桶里,每个桶进行排序,然后合并即为排序数组。
桶排序是把数据划分到不同桶里单独排序即可,相当于hash了几个桶在排序;归并排序是直接分成几个小组,合并在排序,再合并在排序。
10.基数排序:
是一种非比较排序算法,时间复杂度是 O(n) ,是桶排序的扩展,将整数按位数切割成不同的数字,然后按每个位数分别比较。位数不够的前面补0.
比如3位数,先按照个位数排序,在按照十位数排序,最后按照百位数排序。
some code:code也不全,有时间在写吧…
#include<bits/stdc++.h>
using namespace std;
/*
2018.6.18 oder
*/
void swap(int i,int j,vector<int>&nums){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
//1.冒泡排序
vector<int>bubble_sort(vector<int>nums){
for(int i=0;i<nums.size();++i){
int flag=0;//标记用来记录当前循环是否交换了元素,如果没交换就说明已经排好序
for(int j=0;j<nums.size()-i-1;++j){//已经确定位置的就不用遍历了
if(nums[j]>nums[j+1]){
int temp=nums[j+1];
nums[j+1]=nums[j];
nums[j]=temp;
flag=1;
}
}
if(flag==0){
break;
}
}
return nums;
}
//2.鸡尾酒排序
vector<int>cocktail_sort(vector<int>nums){
int left=0;
int right=nums.size()-1;//右面下标是数组长-1
while(left<right){//从左往右遍历
int flag=0;//标记,如果没发生交换则说明已经有序
for(int i=0;i<right;++i){//从左到右遍历
if(nums[i]>nums[i+1]){
swap(i,i+1,nums);
flag=1;
}
}
right--;//最大元素已经放到最右,right--
for(int i=right;i>left;--i){//从右到左遍历
if(nums[i]<nums[i-1]){
swap(i,i-1,nums);
flag=1;
}
}
left++;//最小元素已经放到最左,left++
if(flag==0){
break;
}
}
return nums;
}
//3.简单选择排序
vector<int>selection_sort(vector<int>nums){
for(int i=0;i<nums.size()-1;++i){
int min=i;
for(int j=i+1;j<nums.size();++j){
if(nums[j]<nums[min]){
min=j;
}
}
if(min!=i){
swap(i,min,nums);
}
}
return nums;
}
//4.直接插入排序
vector<int>insertion_sort(vector<int>nums){
for(int i=1;i<nums.size();++i){
int now=nums[i];
int j=i-1;
while(j>=0&&nums[j]>now){//如果大于当前数字,则把后移
nums[j+1]=nums[j];
--j;
}
nums[j+1]=now;
}
return nums;
}
//5.折半插入排序
vector<int> binary_insertion_sort(vector<int>nums){
for(int i=1;i<nums.size();++i){
int left=0;
int right=i-1;
int now =nums[i];
while(left<=right){//找到要插入的位置
int mid=(left+right)/2;
if(nums[mid]>now){
right=mid-1;
}else{
left=mid+1;
}
}
for(int j=i-1;j>=left;--j){//把要插入位置后和之后所有数向后移动一位
nums[j+1]=nums[j];
}
nums[left]=now;
}
return nums;
}
//6.快速排序
//两种partition都可以
int partition(int left,int right,vector<int>&nums){
int pivot =nums[right];
while(left<right){
while(left<right&&nums[left]<=pivot){//从左往右找大于中心元素的
++left;
}
swap(left,right,nums);
while(left<right&&nums[right]>=pivot){//从右往左找小于中心元素的
--right;
}
swap(left,right,nums);
}
return left;
}
int partition(int left,int right,vector<int>&nums){
int pivot =nums[right];
while(left<right){
while(left<right&&nums[left]<=pivot){//从左往右找大于中心元素的
++left;
}
nums[right]=nums[left];
while(left<right&&nums[right]>=pivot){//从右往左找小于中心元素的
--right;
}
nums[left]=nums[right];
}
nums[left]=pivot;
return left;
}
//递归实现
void quick_sort(int left,int right,vector<int>&nums){
if(left>=right){
return;
}
int pivot_index=partition(left,right,nums);//找中间元素索引
quick_sort(left,pivot_index-1,nums);//左半部分快排
quick_sort(pivot_index+1,right,nums);//右半部分快排
}
//非递归实现,用栈,每次入栈的是某一段的left和right,这样一段一段排序
void quick_sort(vector<int>&nums){
stack<int>s;
s.push(0);
s.push(nums.size()-1);
cout<<nums.size()<<endl;
while(!s.empty()){
int right=s.top();
s.pop();
int left=s.top();
s.pop();
int mid = partition(nums,left,right);
for(int i=0;i<nums.size();++i)
cout<<nums[i]<<" ";
cout<<endl;
cout<<mid<<endl;
if(mid>left+1){
s.push(left);
s.push(mid-1);
}
if(mid<right-1){
s.push(mid+1);
s.push(right);
}
}
}
//7.希尔排序,就是分组直接插入,元素之间的距离是gap而不再是1
vector<int>shell_sort(vector<int>nums){
for(int gap=nums.size()/2;gap>=1;gap/=2){//分组距离每次除2
for(int i=gap;i<nums.size();++i){
int j=i;
while(j-gap>=0&&nums[j]<nums[j-gap]){
swap(j,j-gap,nums);
j=j-gap;
}
}
}
return nums;
}
//8.计数排序
vector<int> count_sort(vector<int>&nums,int &k){
vector<int>ct(k,0); //可以不从0开始,因为空间浪费,比如数组从97-120,则可以设置max-min+1的数组大小,第一个位置保存97
for(int i=0;i<nums.size();++i){//初始化计数数组并统计次数
ct[nums[i]]++;
}
for(int i=1;i<ct.size();++i){//更新计数数组,可以表示元素位置
ct[i]+=ct[i-1];
cout<<ct[i]<<" ";
}
cout<<endl;
vector<int>new_nums(nums.size());
for(int i=ct.size(); i>0; i--){//根据元素位置生成新的有序数组,逆向遍历源数组(保证稳定性),根据计数数组中对应的值填充到先的数组中
new_nums[ct[nums[i]]-1]=nums[i];
--ct[nums[i]];
}
return new_nums;
}
//9.桶排序
vector<int> bucket_sort(vector<int>&nums){
vector<int>res;
int n_min=0x7fffffff;
int n_max=0x80000000;
for(int i=0;i<nums.size();++i){
n_max=max(n_max,nums[i]);
n_min=min(n_min,nums[i]);
}
//cout<<"min: "<<n_min<<endl;
//cout<<"max: "<<n_max<<endl;
int ct_per_bucket=(n_max-n_min)/nums.size()+1;//nums分配成若干份
//cout<<"ct_per_bucket: "<<ct_per_bucket<<endl;
vector<vector<int> >bucket(ct_per_bucket,vector<int>(0));//外层是每个桶
for(int i=0;i<nums.size();++i){
int num=(nums[i]-n_min)/nums.size();
//cout<<nums[i]<<" ";
bucket[num].push_back(nums[i]);
}
//cout<<"sort: ";
for(int i=0;i<bucket.size();++i){
sort(bucket[i].begin(),bucket[i].end());
/*for(int j=0;j<bucket[i].size();++j)
cout<<bucket[i][j]<<" ";*/
res.insert(res.end(), bucket[i].begin(), bucket[i].end());
}
return res;
}
//10.归并排序
void _merge(vector<int>&data,int left,int mid, int right){
vector<int> temp(right-left+1);
int i=left,j=mid+1,k=0;
while(i<=mid&&j<=right){
/*
if(data[i]>data[j]){
temp[k++]=data[i++];
}else{
temp[k++]=data[j++];
}*/
if(data[i]>data[j]){
temp[k++]=data[j++];
}else{
temp[k++]=data[i++];
}
}
while(i<=mid){
temp[k++]=data[i++];
}
while(j<=right){
temp[k++]=data[j++];
}
k=0;
for(int i=left;i<=right;++i){
data[i]=temp[k++];
}
}
void merge_sort(vector<int>&data,int left,int right){
if(left>=right)
return;
int mid=(left+right)/2;
merge_sort(data,left,mid);
merge_sort(data,mid+1,right);
_merge(data, left, mid, right);
}
int main(){
int n;
//cin>>n;//n number;7
3 4 1 9 6 2 5 8
vector<int>nums={7,3,4,1,9,6,2,5,8};
vector<int>res;
/*while(n--){
int now;
cin>>now;
nums.push_back(now);
} */
//res=Bubble_Sort(nums);
//res=Cocktail_sort(nums);
//res= selection_sort(nums);
//res=insertion_sort(nums);
//merge_sort(nums,0,nums.size()-1);
/*quick_sort(0,nums.size()-1,nums);
res=nums;*/
res=binary_insertion_sort(nums);
//res=shell_sort(nums);
for(int i=0;i<nums.size();++i){
cout<<res[i]<<" ";
}
cout<<endl;
return 0;
}
//11.三路快排
/*
思想其实就是拿第一个当pivot,设置三个指针lt=l、i=l+1、rt=r+1,每次比较arr[i]和arr[lt],arr[i]小于arr[lt]就swap(arr[i],arr[lt+1]),i++,lt++;
如果大于就swap(arr[i],arr[rt-1]),lt--;等于就i++;
思想就是每次i跟pivot比,把小于的放最左可放位置lt+1,此时lt++,i++,可放位置和比较的都往后顺延;
如果大于就把它放到最后,此时最后的数交换了过来,这个数还没判断,所以不进行i++,只进行rt--。
最后lt位置的数小于pivot,交换它和pivot,会在中间留下等于pivot的数的连续序列,在递归l到lt-1和rt到r。
*/
void quicksort3(vector<int>nums, int l, int r){
int lt = l;
int rt = r+1;
int i = l+1;
int pivot = nums[l];
while(i<rt){
if(nums[i] < pivot){
swap(nums[i], nums[lt+1]);
lt++;
i++;
}else if(nums[i] > pivot){
swap(nums[i], nums[rt-1]);
rt--;
}else{
i++;
}
}
swap(nums[l], nums[lt]);//因为第一个元素是pivot,把他和最后一个小于pivot的数交换,
quicksort(nums, l, lt-1);//因为lt是pivot,所以l到lt-1
quicksort(nums, rt, r);
}
//12.三路快排2
template <typename Item>
void quick_sort(Item a[], int left, int right) {
if (right-left < M) // prevent little array recursion
return;
// median-of-three
compare_exchange(a[left], a[(left+right)/2]);
compare_exchange(a[left], a[right]);
compare_exchange(a[right], a[(left+right)/2]);
// three-way-partition
Item v = a[right];
int i = left-1, p = left-1, j = right, q = right;
for (;;) {
while (a[++i] < v) {}
while (a[--j] > v)
if (j == left)
break;
if (i >= j)
break;
exchange(a[i], a[j]);
if (a[i] == v)
exchange(a[++p], a[i]);
if (a[j] == v)
exchange(a[--q], a[j]);
}
exchange(a[i], a[right]);
// move equal elements to middle of array
j = i-1;
i = i+1;
int k;
for (k = left; k <= p && j > p; ++k, --j)
exchange(a[k], a[j]);
j -= p-k+1;
for (k = right-1; k >= q && i < q; --k, ++i)
exchange(a[k], a[i]);
i += k-q+1;
quick_sort(a, left, j);
quick_sort(a, i, right);
}
//13.堆排序
/*
几个函数:
1.adj_max_heap调整堆函数,从i开始,跟他的儿子结点比较找一个最大的索引largest,如果是i,不操作;
不是i,和i交换位置,并递归调用从largest开始继续调整
2.build_heap建堆函数, 从len/2开始到0的位置调整堆,for(int i=len/2;i>0;--i),其实就是最后一个非叶子结点开始。
3.heap_sort堆排序函数,首先要建堆,然后遍历所有元素,依次把第一个元素交换到最后,len--,这个时候从第一个元素调整堆,只不过长度-1了,这个时候最后的位置保存的就是最大或者最小的元素。
其实大顶堆排序完是升序,最大的放最后,小顶堆相反。
步骤:
0.把数据表示为完全二叉树,数组元素本来就可以表示为完全二叉树,用下标结点i的左儿子为2*i,右儿子为2*i+1,根节点为1
1.建堆,此时需要让每个父节点的数值 > 左右儿子节点;从倒数第一个父节点(length/2)开始调整,一直调整到根节点,在调整过程中如果最大值是父节点则不需要操作,如果最大值是儿子节点2*i,则需要和父节点交换数值,再以交换上来的那个儿子节点位置2*i为父节点继续往下调整;
2.排序,调整完的堆最大值一定在根节点,此时把他交换到最后一个节点位置,length--,在从根节点调整堆(注意此时的length已经不包含最后一个节点),重复操作,则把数组调整为升序。
*/
void adj_max_heap(int *datas, int length,int i){
int left,right,largest;
int temp;
left=2*i;
right=2*i+1;
if(left<=length&&datas[left]>datas[i]){
largest=left;
}else{
largest=i;
}
if(right<=length&&datas[right]>datas[largest]){
largest=right;
}
if(largest!=i){
temp=datas[i];
datas[i]=datas[largest];
datas[largest]=temp;
adj_max_heap(datas,length,largest);
}
}
void build_heap(int *datas, int length){
//int i;
for(int i=len/2;i>0;--i){
adj_max_heap(datas,length,i);
}
}
void heap_sort(int *datas, int length){
build_heap(datas,length);//建堆
int i=length,temp;
while(i>1){
temp=datas[i];
datas[i]=datas[1];
datas[1]=datas[i];
adj_max_heap(datas,i-1,1);
}
}
//14.链表快排
//也是找中间的pivot,但是partition的时候,pivot=left->val,p和q的指针,从头往后遍历,
//q<pivot的时候就交换q和p的数值
ListNode* sortList(ListNode* head) {
if(head==nullptr||head->next==nullptr)
return head;
/*ListNode* end=head;
while(end->next)
end=end->next;*/
Quick_sort(head, nullptr);
return head;
}
void Quick_sort(ListNode* begin,ListNode* end){
if(begin==end)
return;
ListNode * mid = partition(begin, end);
Quick_sort(begin, mid);
Quick_sort(mid->next, end);
}
ListNode* partition(ListNode* begin, ListNode* end){
int pivot = begin->val;
ListNode * p = begin;
ListNode * q = begin->next;
while(q!=end){
if(q->val<pivot){
p=p->next;
swap(q->val,p->val);
}
q=q->next;
}
swap(p->val,begin->val);//把最后一个<pivot的数放到最前(和pivot交换)
return p;
}