排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
1、交换排序
(1)冒泡排序:
- public void BubbleSort(int[] x)
- {
- for(int i=0;i<x.length-1;i++) //若数组长度为n,则执行n-1次“冒泡”即可(每次可找出剩余元素的最大值,剩下的一个为最小值)
- {
- for(int j=0;j<x.length-i-1;j++) //执行第i次“冒泡的时候”在数组尾部已经有i-1个排好序的“最大值”
- {
- if(x[j]>x[j+1])
- swap(x,j,j+1);
- }
- }
- }
- public void swap(int[] x,int i,int j)
- {
- int t=x[i];
- x[i]=x[j];
- x[j]=t;
- }
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
(2)快速排序:
- public void Quicksort(int[] x)
- {
- HelperOfQuicksort(x,0,x.length-1);
- }
- public void HelperOfQuicksort(int[] x,int low,int high)
- {
- if(low<high)
- {
- int mid=getMiddleIndex(x,low,high); //mid左面的部分都小于等于x[mid],mid右面的部分都大于等于x[mid]
- HelperOfQuicksort(x,low,mid-1);
- HelperOfQuicksort(x,mid+1,high);
- }
- }
- public int getMiddleIndex(int[] x,int low,int high)
- {
- int tmp=x[low]; //选当前无序部分的第一个元素做标兵
- while(low<high)
- {
- while(low<high&&x[high]>=tmp)
- high--;
- x[low]=x[high]; //x[high]是本次循环找到的小于tmp的值
- while(low<high&&x[low]<=tmp)
- low++;
- x[high]=x[low]; //x[low]是本次循环找到的大于tmp的值
- }
- x[low]=tmp; //循环结束后,对应本次函数处理的部分数组(假设从a到b),a到low-1为小于等于tmp的部分,low+1到b为大于等于tmp的部分
- return low; //此时low=high
- }
时间复杂度:O(n^logn)
空间复杂度:O(n^logn)
稳定性:不稳定
2、插入排序
(1)直接插入排序:
- public void StraightInsertionSort(int[] x)
- {
- if(x.length==1)
- return ;
- for(int i=1;i<x.length;i++) //将数组中的1到x.length-1位置的元素分别插入前面排好序的部分的相应位置(起始的排好序的部分是x[0])
- {
- int cur=x[i]; //当前待插入元素
- int m=i-1; //从当前元素的前一个元素开始比较
- for(;m>=0&&x[m]>cur;m--) //前面的元素如果>当前待插入元素则将此元素在数组中后移一个位置
- x[m+1]=x[m];
- x[m+1]=cur; //跳出循环时,证明m是小于待插入元素的位置(或者m=-1),将待插入元素插在m+1的位置
- }
- }
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
(2)希尔排序:
- public void ShellSort(int[] x)
- {
- int gap=x.length/2;
- while(gap>=1) //最外层循环控制步长
- {
- for(int i=0;i<gap;i++) //若gap=3,则有三组子序列需要分别进行直接插入排序
- {
- for(int j=i+gap;j<x.length;j+=gap) //对每一组子序列进行单独进行直接插入排序
- {
- int cur=x[j];
- int index=j-gap;
- for(;index>=0&&x[index]>cur;index-=gap)
- x[index+gap]=x[index];
- x[index+gap]=cur;
- }
- }
- gap/=2;
- }
- }
时间复杂度:O(n^1.25)到O((1.6n)^1.25)(经验公式)
空间复杂度:O(1)
稳定性:不稳定
3、选择排序
(1)简单选择排序:
- public void SimpleSelectionSort(int[] x)
- {
- for(int i=0;i<=x.length-2;i++) //每次找到当前数组剩余元素中的最小值,和剩余部分元素的首元素交换。五个元素交换四次即可。
- { //i指向当前未排序部分(即剩余部分)的首元素
- int curMin=Integer.MAX_VALUE;
- int index=-1;
- for(int j=i;j<x.length;j++) //从i开始直到数组最后找到最小值和它的index
- {
- if(x[j]<curMin)
- {
- index=j;
- curMin=x[j];
- }
- }
- swap(x,i,index);
- }
- }
- public void swap(int[] x,int i,int j)
- {
- int t=x[i];
- x[i]=x[j];
- x[j]=t;
- }
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定([2,2,1],循环首次执行之后1与首元素,即第一个2交换,变成[1,2,2]。第一个二跑到了最后,在原来第二个2之后)
(2)堆排序
- public void Heapsort(int[] x)
- {
- //每次将0到i的元素建堆(只是把数组想作堆结构)后,找到堆顶的元素(即首元素)与i位置的元素交换。即0到i为当前无序部分,i之后都是有序部分
- for(int i=x.length-1;i>=1;i--)
- {
- BuildHeap(x,i);
- swap(x,0,i);
- }
- }
- public void BuildHeap(int[] x,int LastIndex)
- {
- int k=-1;
- //从LastIndex到1位置的元素分别找到它们的父结点,如果父结点的值比它的全部子节点的值都大,则不变;否则将父结点的值与子节点中的较大值互换(可能存在当前父结点没有右子节点的情况)
- for(int i=LastIndex;i>=1;i--)
- {
- int curParentNode=(i-1)/2; //(当前结点下标-1)/2即为它的父结点的下标,无论左右结点都适用
- //目前的k指向上次循环处理过的父结点,如果k与curParentNode相同,则证明当前的父结点已经处理过了(即它的值肯定大于等于它的子节点的值)
- if(curParentNode==k)
- continue;
- k=curParentNode; //若当前的父结点没有处理过,则更新k
- int biggerSonNode=-1; //biggerSonNode指向当前父结点的子节点中值较大的子节点
- if(k*2+2<=LastIndex) //k*2+2指向当前父结点的右结点,如果它大于LastIndex,则证明当前父结点没有右子结点
- biggerSonNode=x[k*2+1]>=x[k*2+2]?(k*2+1):(k*2+2);
- else
- biggerSonNode=k*2+1;
- if(x[biggerSonNode]>x[k]) //如子节点中较大值大于父结点的值,则交换,保证每一个父结点的值都大于它的所有子节点,最终保证堆顶的元素值最大
- swap(x,biggerSonNode,k);
- }
- }
- public void swap(int[] x,int i,int j)
- {
- int t=x[i];
- x[i]=x[j];
- x[j]=t;
- }
时间复杂度:O(n*logn)
空间复杂度:O(1)
稳定性:不稳定
4、归并排序
- public void MergeSort(int[] x)
- {
- HelperSort(x,0,x.length-1);
- }
- public void HelperSort(int[] x,int start,int end) //将当前待处理的部分继续划分为两部分
- {
- if(start>=end)
- return;
- int mid=(start+end)/2;
- int start1=start; //第一个部分的起始下标
- int end1=mid; //第一个部分的终止下标
- int start2=mid+1; //第二个部分的起始下标
- int end2=end; //第二个部分的终止下标
- HelperSort(x,start1,end1);
- HelperSort(x,start2,end2);
- merge(x,start1,end1,start2,end2); //将划分好的两部分归并。每次归并的两个部分分别都已经是有序的了。
- }
- public void merge(int[] x,int FirStart,int FirEnd,int SecStart,int SecEnd)
- {
- int[] temp=new int[SecEnd-FirStart+1]; //临时生成一个新的数组用来存储归并之后的两个部分的合成数组
- int tempIndex=0;
- int first=FirStart; //指向第一个部分的首个元素
- int second=SecStart; //指向第二个部分的首个元素
- while(first<=FirEnd&&second<=SecEnd) //直到遍历到某个部分的最后一个元素
- {
- if(x[first]<=x[second]) //first和second始终指向当前第一(二)个部分剩余元素的最小值
- {
- temp[tempIndex]=x[first];
- tempIndex++;
- first++;
- }
- else
- {
- temp[tempIndex]=x[second];
- tempIndex++;
- second++;
- }
- }
- while(first<=FirEnd) //若第一个部分还有剩余没有并入新的临时数组,则直接并入
- temp[tempIndex++]=x[first++];
- while(second<=SecEnd) //若第二个部分还有剩余没有并入新的临时数组,则直接并入
- temp[tempIndex++]=x[second++];
- for(int i=0;i<tempIndex;i++) //将并好的有序数组写入原始数组的相应位置(从第一部分的起始到第二部分的结束(因为这两部分在原数组处在相邻位置))
- x[FirStart+i]=temp[i];
- }
时间复杂度:O(n*logn)
空间复杂度:O(n)
稳定性:稳定
5、基数排序
- public void RadixSort(int[] x)
- {
- int max=Integer.MIN_VALUE;
- for(int i=0;i<x.length;i++) //找到数组中的最大值
- {
- if(x[i]>max)
- max=x[i];
- }
- int maxbit=0;
- while(max>0) //找到最大位数
- {
- max/=10;
- maxbit++;
- }
- int radix=10; //0~9共10个桶
- ArrayList<Queue> list=new ArrayList<>(); //用来存储桶的list。list.get(1)存储着桶1
- for(int i=0;i<radix;i++)
- {
- Queue<Integer> q=new LinkedList<>();
- list.add(q);
- }
- //x中最初存储[5,1,3,7,23,72,7].确认最大位数为十位,故只需整体入桶两次即可(先按照个位后按照十位)
- //首先按照个位入桶
- //桶1:1
- //桶2:72
- //桶3:3,23
- //桶5:5
- //桶7:7,7
- //其余的桶为空
- //按桶从小到大,同一个桶从左到右的顺序将全部元素放回数组x中(由于使用的是队列,每次头元素入x的同时就会清空桶(队列)中的头元素)
- //x为[1,72,3,23,5,7,7],再按照十位入桶
- //桶0:1,3,5,7,7
- //桶2:23
- //桶7:72
- //其余的桶为空
- //按桶从小到大,同一个桶从左到右的顺序将全部元素放回数组x中
- //x为[1,3,5,7,7,23,72],完毕
- int dividend=1;
- for(int i=0;i<maxbit;i++) //从低位到高位分别处理
- {
- for(int j1=0;j1<x.length;j1++) //将所有元素按照当前检测位的数字大小入桶
- {
- int index=(x[j1]/dividend)%radix;
- list.get(index).offer(x[j1]);
- }
- dividend*=10;
- int m=0;
- for(int j2=0;j2<radix;j2++) //按桶从小到大,同一个桶从左到右的顺序将全部元素放回数组x中
- {
- while(list.get(j2).size()!=0)
- {
- x[m]=(int) list.get(j2).poll();
- m++;
- }
- }
- }
- }
时间复杂度:O(d*n) d为长度(对应代码中maxbit),n为数组长度
空间复杂度:O(n)
稳定性:稳定