排序算法思路整理
个人理解,主要是给自己看的,文字较为通俗,见谅
是否稳定:是否会改变相同元素的相对顺序
冒泡排序(稳定)
思路 :以升序为例,从第一个元素开始,与下一个元素做比较,如果前一个元素大于后一个元素,则进行交换,否则直接++,每次大的元素做交换往后移,则第一趟遍历下来,最大的元素,已经到了最后,重复操作,n个元素需要 n-1 趟的遍历就会完全有序。
void Bubblesort(int *arr,int len)
{
int tmp=0;
for(int i=0;i<len-1;i++)
{
//bool flag = false; 优化
for(int j=0;j<len-1-i;j++)
{
if(arr[j] > arr[j+1])
{
tmp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
//flag = true;
}
}
//if(flag == false) break;
}
}
时间复杂度 : O(n^2)
空间复杂度 : O(1)
优化方面 :可以设置一个bool类型的变量做标识,初始化为false,交换就赋值为true,若某一趟遍历没有发生交换,则说明元素已经完全有序,直接跳出循环,无需再遍历,但是个人认为优化其实无必要,既然已经用了冒泡,那么再怎么优化,效率方面提升也不会很大。
快速排序(不稳定)
递归思路 :以升序为例,从n个元素从选出一个元素作为基准(一般为待排序元素序列的首元素),用一变量保存,取两个数left与right分别做待排序元素序列中第一个元素与最后一个元素的下标,从right开始,往前遍历,直到找到第一个小于基准的元素,将其值赋给left下标的元素,再从left开始往后遍历,直到找到第一个大于基准的元素,将其值赋给right下标的元素,只要left<right,就反复循环操作,跳出循环时应该是left==right,该位置就是基准元素在完全有序时应在的位置,将基准赋在该位置并返回下标,此为一次划分。快排递归思路就是调用一次划分函数,当返回值左右两边的待排序元素长度大于等于2时,就需要继续进行划分,若左边待排序元素需要继续划分,则原left不变,以返回值-1为新right,递归调动快排函数,若右边待排序元素需要继续划分,则原right不变,以返回值+1为新left递归调动快排函数,直到待排序元素只剩一个就自然有序了,那么整体序列也就完全有序了。
static int Partition(int *arr,int left,int right)
{
int tmp=arr[left];
while(left<right)
{
while(left<right && arr[right]>=tmp)
{
right--;
}
arr[left] = arr[right];
while(left<right && arr[left]<=tmp)
{
left++;
}
arr[right] = arr[left];
}
arr[left] = tmp;
return left;
}
static void Quick(int *arr,int left,int right)
{
int tmp = Partition(arr,left,right);
if(left<tmp-1)
{
Quick(arr,left,tmp-1);
}
if(right>tmp+1)
{
Quick(arr,tmp+1,right);
}
}
void Quicksort(int *arr,int len)
{
Quick(arr,0,len-1);
}
时间复杂度 :
空间复杂度 :
非递归实现 :
static int Partition(int *arr,int left,int right)
{
int tmp=arr[left];
while(left<right)
{
while(left<right && arr[right]>=tmp)
{
right--;
}
arr[left] = arr[right];
while(left<right && arr[left]<=tmp)
{
left++;
}
arr[right] = arr[left];
}
arr[left] = tmp;
return left;
}
static void Quick(int *arr,int left,int right)
{
assert(left<right);
stack<int> m;
int mid=Partition(arr,left,right);
if(left<mid-1)
{
m.push(left);
m.push(mid-1);
}
if(right>mid+1)
{
m.push(mid+1);
m.push(right);
}
while(!m.empty())
{
int r=m.top();
m.pop();
int l=m.top();
m.pop();
mid=Partiton(arr,l,r);
if(l<mid-1)
{
m.push(l);
m.push(mid-1);
}
if(r>mid+1)
{
m.push(mid+1);
m.push(r);
}
}
}
void Quicksort(int *arr,int len)
{
Quick(arr,0,len-1);
}
直接插入排序(稳定)
思路 :以升序为例,从第2个元素开始遍历,前面只有一个元素,故可直接认为有序,让第二个元素与前面的元素做比较,找到第一个比自身小的元素,直接插在其后面,然后后面的元素,以此类推
void Insertsort(int *arr,int len)
{
int tmp=0;
for(int i=1;i<len;i++)
{
tmp=arr[i];
int j=i-1;
while(j>=0 && arr[j]>tmp)
{
arr[j+1]=arr[j];
j--;
}
arr[j+1]=tmp;
}
}
时间复杂度 :O(n^2)
空间复杂度 :O(1)
希尔排序(不稳定)
思路 :本质而言,其实还是插入排序,按下标的一定增量将待排序元素序列分成若干组,然后使用直接插入排序,然后缩小该增量,继续进行直接插入排序,当该增量缩小至1时,整个序列就是一组,再进行直接插入排序。
static void Shell(int *arr,int len,int gap)
{
for(int i=gap;i<len;i++)
{
int tmp=arr[i];
int j=i-gap;
while(j>=0 && arr[j]>tmp)
{
arr[j+gap]=arr[j];
j-=gap;
}
arr[j+gap]=tmp;
}
}
void Shellsort(int *arr,int len)
{
int gaps[]={5,3,1};
for(int i=0 ; i < sizeof(gaps)/sizeof(gaps[0]) ; i++)
{
Shell(arr,len,gaps[i]);
}
}
时间复杂度 :O(n^1.3)
空间复杂度 :O(1)
直接选择排序(不稳定)
思路 :从待排序序列中找到值最小的元素,如果该元素不是第一个元素,就与第一个元素进行交换,然后从余下元素中找到最小元素,若不是第二个元素与第二个元素交换,以此类推。
void Selectsort(int *arr,int len)
{
int tmp=0;
for(int i=0;i<len-1;i++)
{
int min=i;
for(int j=i+1;j<len;j++)
{
if(arr[j]<arr[min])
{
min=j;
}
}
if(min != i)
{
tmp=arr[i];
arr[i]=arr[min];
arr[min]=tmp;
}
}
}
时间复杂度 :O(n^2)
空间复杂度 :O(1)
堆排序(不稳定)
思路 :大顶堆(每个结点的值都大于等于左右孩子节点的值),小顶堆(每个结点的值都小等于左右孩子节点的值),堆排序就是构建一个大顶堆(升序用大顶堆,降序用小顶堆),此时根节点就是最大值,将其与末尾结点交换,然后将余下元素结点,重新调整为大顶堆,找出次大值与倒数第二个交换,以此类推,直到序列完全有序。
void Adjust(int *arr,int start,int end)
{
int tmp=0;
for(int i=start*2+1;i<=end;i=i*2+1)
{
if(i+1<=end && arr[i]<arr[i+1])
{
i++;
}
if(arr[(i-1)/2]<arr[i])
{
tmp=arr[(i-1)/2];
arr[(i-1)/2]=arr[i];
arr[i]=tmp;
}
}
}
void Heapsort(int *arr,int len)
{
for(int i=(len-1-1)/2;i>=0;i--)
{
Adjust(arr,i,len-1);
}
for(int j=len-1;j>=0;j--)
{
int tmp=arr[j];
arr[j]=arr[0];
arr[0]=tmp;
Adjust(arr,0,j-1);
}
}
时间复杂度 :
空间复杂度 : O(1)
基数排序(稳定)
思路 :构建10个队列分别标识09,找到待排序序列中元素的最大值,并得到其位数,这是元素入队出队的次数,由最低位开始,按照该位上的值入相应的队列,然后按09顺序出队,再按高一位的值进行入队出队,直至最高位,最后一次出队时,则完全有序
int Gerfig(int *arr,int len)
{
int max=0;
for(int i=0;i<len;i++)
{
if(arr[i]>max)
{
max=arr[i];
}
}
int fig=0;
while(max!=0)
{
max/=10;
fig++;
}
return fig;
}
int Getnum(int n,int fig)
{
while(fig!=0)
{
n/=10;
fig--;
}
return n%10;
}
void Radix(int *arr,int len,int fig)
{
queue<int> ma[10];
int m;
for(int i=0;i<len;i++)
{
m=Getnum(arr[i],fig);
ma[m].push(arr[i])
}
int k=0;
for(int j=0;j<10;j++)
{
while(!ma[j].empty())
{
arr[k++]=ma[j].front();
ma[j].pop();
}
}
}
void Radixsort(int *arr,int len)
{
int fig=Getfig(arr,len);
for(int i=0;i<fig;i++)
{
Radix(arr,len,i);
}
}
时间复杂度 :O(d(n+r)) //d长度 r关键字基数 n关键字个数
空间复杂度 :O(n+rd)
归并排序(稳定)
思路 :简单来说,归并排序就是将两个有序序列合并成一个有序序列,而当一个序列只包含一个元素时,自然可将它认为有序,故可将n个元素的待排序序列,当作n个有序子序列,然后利用归并排序不断两两合并,直到最后只剩下两个有序序列再进行合并,成为一个完全有序的序列
static void Merge(int *arr,int len,int gap)
{
int low1=0;
int high1=low1+gap-1;
int low2=high1+1;
int high2=low2+gap-1<len?low2+gap-1:len-1;
int *brr=(int *)malloc(len*sizeof(int));
int i=0;
while(low2<len)
{
while(low1<=high1 && low2<=high2)
{
if(arr[low1]<=arr[low2])
{
brr[i++]=arr[low1++];
}
else
{
brr[i++]=arr[low2++];
}
}
while(low1<=high1)
{
brr[i++]=arr[low1++];
}
while(low2<=high2)
{
brr[i++]=arr[low2++];
}
low1=high2+1;
high1=low1+gap-1;
low2=high1+1;
high2=low2+gap-1<len?low2+gap-1:len-1;
}
while(low1<len)
{
brr[i++]=arr[low1++];
}
for(i=0;i<len;i++)
{
arr[i]=brr[i];
}
free(brr);
}
void Mergesort(int *arr,int len)
{
for(int i=1;i<len;i*=2)
{
Merge(arr,len,i);
}
}
时间复杂度 :
空间复杂度 : O(n)