上一篇文章中讲了数组的基础以及三种排序方法,这篇文章将延续上篇的思路,继续介绍八大排序算法中的另外五种。
1.八大排序
1)希尔排序
例:给定一个n个元素的数组,数组下标从0开始,采用希尔排序将数组按照升序排列。
思想:希尔排序是基于插入排序的一种改进算法,思路为先将数组分为n/2组,对每一组进行插入排序;再将新的数组分成n/2/2组,对每一组进行插入排序,依次类推,直到组数为1位为止,插入排序后保证数组变为有序。代码实现如下。
public void shellSort(int[] arr){
int temp = 0;
//将数组分为gap组,即为每相隔gap距离的元素为同一组
for(int gap=arr.length/2;gap>0;gap/=2){
//循环中实际是一个插入排序
for(int i=gap;i<arr.length;i++){
//从i-gap开始与前面每相隔gap距离的元素进行比较,插入排序的思想,只不过这里用了交换而不是位移
for(int j=i-gap;j>=0;j-=gap){
if(arr[j]>arr[j+gap]){
temp = arr[j];
arr[j] = arr[j+gap];
arr[j+gap] = temp;
}
}
}
}
for(int n=0;n<arr.length;n++){
System.out.println(arr[n]);
}
}
2)归并排序
例:给定一个n个元素的数组,数组下标从0开始,采用归并排序将数组按照升序排列。
思想:归并排序的思路是将数组分解为数量为n的最小单元,对每个单元进行排序后将其两两合并为数量为2n的单元;依此类推,直至将所有单元合并。归并排序是一种分而治之的思想。代码实现如下。
public void mergeSort(int[] arr,int l,int r){
int mid = (l+r)/2;
if(l<r){
//先从中间开始递归将数组分解为最小单元
mergeSort(arr,l,mid);
mergeSort(arr,mid+1,r);
//将分解后的单元分别进行排序与合并
merge(arr,l,mid,r);
}
}
private void merge(int[] arr,int l,int mid,int r){
int[] temp = new int[r-l+1];
int i = l;
int j = mid+1;
int c = 0;
while (i<=mid && j<=r){
//比较大小,并合并两个数组为temp
if(arr[i]<=arr[j]){
temp[c] = arr[i];
i++;
}else{
temp[c] = arr[j];
j++;
}
c++;
}
//上面的循环执行完之后,肯定有一个单元a全部完成合并,另一个单元b中会剩下大于单元a中所有元素的值,需要将单元b中剩下的元素也全部合并到temp中。
while (i<=mid){
temp[c] = arr[i];
c++;
i++;
}
while (j<=r){
temp[c] = arr[j];
c++;
j++;
}
c=0;
int tempL = l;
//按照temp中排好的顺序调整原数组arr中的元素顺序
while (tempL<=r){
arr[tempL] = temp[c];
c++;
tempL++;
}
}
3)快速排序
例:给定一个n个元素的数组,数组下标从0开始,采用快速排序将数组按照升序排列。
思想:顾名思义,快排是八大排序算法中性能最好的。先选择一个数作为基准数pivot,以pivot为基准用双指针遍历数组,将小于pivot的元素集中到数组左边,大于pivot的元素集中到数组右边,双指针会合的位置即为pivot应该在的位置;此时将左右两个数组分别递归进行上述操作,直至整个数组变得有序。快速排序也是分而治之的思想。代码实现如下。
public void quickSort(int[] arr,int l,int r){
//当l>=r时,说明这一轮排序结束
if(l>=r){
return;
}
//取最左边的元素作为基准数
int pivot = arr[l];
int i = l;
int j = r;
while (i<j){
//如果右边的元素大于基准数,将右指针r前移,直到遇到小于基准数的元素为止
while (arr[j]>=pivot && i<j){
j--;
}
//如果左边的元素小于基准数,将左指针l后移,直到遇到大于基准数的元素为止
while (arr[i]<=pivot && i<j){
i++;
}
//交换两个元素,保证左边全部元素小于基准值,右边全部元素大于基准值
if(i<j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//循环结束后i即是基准数应该在的位置,由于取了最左边的数为基准数,所以需要将基准数和i位置的元素交换
arr[l] = arr[i];
arr[i] = pivot;
//递归对左右两侧数组分别再进行排序
quickSort(arr,l,i-1);
quickSort(arr,i+1,r);
}
4)计数排序
例:给定一个n个元素的数组,数组下标从0开始,采用计数排序将数组按照升序排列。
思想:首先遍历整个数组,找出数组中的最小元素min与最大元素max;声明一个长度为max-min+1的数组counter作为计数器;使min值对应counter数组下标0的位置,再次遍历整个数组,每遍历一个元素就在counter中对应的位置加1;遍历counter数组即可排列原数组的顺序。由该思路可以看出,计数排序并不依赖于元素之间的比较,但是不适用于元素差值过大的数组和元素不是整数的数组。计数排序的代码实现如下。
public void countSort(int[] arr){
int min = arr[0];
int max = arr[0];
//先遍历数组找出数组的最大值和最小值
for(int i=0;i<arr.length;i++){
if(arr[i]<min){
min = arr[i];
}
if(arr[i]>max){
max = arr[i];
}
}
//声明计数器
int[] counter = new int[max-min+1];
//遍历数组进行计数
for(int i=0;i<arr.length;i++){
counter[arr[i]-min]++;
}
int index = 0;
//遍历计数器,将对应的元素放回原数组
for(int i=0;i<counter.length;i++){
while (counter[i]>0){
arr[index] = i+min;
index++;
counter[i]--;
}
}
for(int n=0;n<arr.length;n++){
System.out.println(arr[n]);
}
}
5)基数排序
例:给定一个n个元素的数组,数组下标从0开始,采用基数排序将数组按照升序排列。
思想:基数排序思路是将原数组按照位数进行排序,先按个位数的大小将原数组排序,再按十位数的大小排序,依此类推,直到整个数组变得有序。基数排序是一种以空间换时间的思路,当数据的数量级很大时,会非常占用空间。代码实现如下。
public void radixSort(int[] arr){
//基数,代表位数,1为个位,10为十位,依此类推
int digit = 1;
int max = arr[0];
//遍历获取数组中的最大值
for(int i=0;i<arr.length;i++){
if(arr[i]>max){
max = arr[i];
}
}
//获取最大值的数量级
int maxDigit = (int)Math.pow(10,(max+"").length());
//创建数据桶,用来记录每个位数计数时数据所在位置
int[][] bucket = new int[10][arr.length];
//临时数组,用来记录每个位数时0-9出现的次数
int[] temp = new int[10];
while (digit<maxDigit){
//以digit为基准记录数据到数据桶中(digit为1说明该次排序是按个位数排序,digit为10说明该次排序是按十位数排序,依此类推)
for(int i=0;i<arr.length;i++){
int d = (arr[i]/digit)%10;
bucket[d][temp[d]] = arr[i];
temp[d]++;
}
//类似计数排序过程,只不过数据桶中记录了该次排序具体的元素值,按照下标取值放回原数组即可
int index = 0;
for(int i=0;i<10;i++){
int indexF = 0;
//按照temp中记录的数量从数据桶中取回数据
while (temp[i]>0){
arr[index] = bucket[i][indexF];
index++;
temp[i]--;
indexF++;
}
}
//位数提高一位,继续排序
digit*=10;
}
for(int n=0;n<arr.length;n++){
System.out.println(arr[n]);
}
}
2.习题
文章内容为英雄哥算法星球的个人学习总结,B站英雄哥直播间:https://live.bilibili.com/24513717 ,每日凌晨5点到8点直播刷题。
注:本文章搬运自个人博客。