最近因为找实习,重新梳理了一遍数据结构中常用排序算法,仅供学习参考,如有不足之处,敬请指正。
目录
插入排序
-
直接插入排序
基本思路:将数组看成一个有序数组,一个无序数组
public static void insertSort(int[] arr){
//数组是空或者数组里面只有一个元素,没必要排序
if (null == arr || arr.length < 2){
return;
}
for(int i=1;i<n;i++){
int temp = arr[i];
int j=i;
while(j-1>=0 && temp<arr[j-1]){
arr[j]=arr[j-1];
j--;
}
arr[j]=temp;
}
}
-
折半插入排序
public static void binaryInsertSort(int[] arr){
for(int i=1;i<arr.length;i++){
if(arr[i] < arr[i-1]){
int temp=arr[i];
int low=0;
int high=i-1;
while(low<=high){
int mid=(low+high)/2;
if(arr[mid] < temp){
low=mid+1;
}else{
high=mid-1;
}
}
//元素后移,为插入temp做准备
for(int j=i;j>low;j--){
arr[j]=arr[j-1];
}
arr[low]=temp;
}
}
}
-
希尔排序
基本思路:最小增量排序,原理:先将待排序的数组元素分成多分子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序序列“基本有序后”,再对所有元素进行一次直接插入排序。
//增量设置:d1 = n/2,d2 = d1/2 ...
//举例一下:{9,8,7,6,5,4,3,2,1,0} 10个数,现分为5组(9,4),(8,3),(7,2),(6,1),(5,0),然后分别对每组进行直接插入排序得到:(4,9),(3,8),(2,7),(1,6),(0,5),再将这5组分为2组(4,3,2,1,0),(9,8,7,6,5)分别对这两组进行直插排序,得:(0,1,2,3,4),(5,6,7,8,9)最终有序。
public class Sort{
public static void shellSort(int arr[]){
int len=arr.length;
int i,j;
int h;
int temp;
for(h=len/2;h>0;h=h/2){
for(i=h;i<len;i++){
j=i;
temp=arr[j];
while(j-h>=0 && arr[j-h]>temp){
arr[j]=arr[j-h];
j=j-h;
}
arr[j]=temp;
}
}
}
}
交换排序
-
冒泡排序
基本思路:每次比较相邻两个元素,如果它们的顺序错误就把它们交换
//第一层循环,总比较次数n-1:0-n~2(i<n-1)
//第二层循环,未归位的位置比较
public class bubbleSort{
public static void bubbleSort(int[] array){
if(null=array || array.length < 2){
return;
}
for(int i=0;i<array.length-1;i++){
for(int j=0;j<array.length-i-1;j++){
if(a[j]>a[j+1]){
int temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
public static void main(String [] args){
int[] array={10,9,8,7,6,5,4,3,2,1};
bubbleSort(array);
for(int num:array){
System.out.print(num+" ");
}
}
}
-
快速排序
基本思路:i和j,分别指向第一个和最后一个,i像后移动,j向前移动,选第一个数为标准(一般这样做,当然快排的关键就是这个“标准”的选取),从后面开始,找到第一个比标准小的数,互换位置,然后再从前面,找到第一个比标准大的数,互换位置,第一趟的结果就是标准左边的都小于标准,右边的都大于标准(但不一定有序),分成两拨后,继续递归的使用上述方法,最终有序!
//快速排序
//定义一个阈值,分别从最左面和最右面向中间遍历元素,左面找到一个大于阈值的数据便停止,
//右边找到一个小于阈值的数据便停止,如果此时左右两边都还没有走到中间,
//则交换左面大于阈值的数据和右面小于阈值的数据;重复上述过程,直到左面指针和右面指针相遇,
//此时左面数据均小于阈值,右面数据均大于阈值,划分结束。划分结束后,数据仍然是无序的,
//但更接近于有序。
public class quickSort{
public static void sort(int arr[],int low,int high){
int i,j;
int index;
if(null=arr||low>=high)
return;
i=low;
j=high;
index=array[i];
while(i<j){
while(i<j && arr[j]>=index)
j--;
if(i<j)
arr[i++]=arr[j];
while(i<j && arr[i]<=index)
i++;
if(i<j)
arr[j--]=arr[i];
}
arr[i]=index;
sort(array,low,i-1);
sort(array,i+1,high);
}
public static void quickSort(int arr[]){
sort(arr,0,arr.length-1)
}
}
选择排序
-
简单选择排序
基本思路:
//首先找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置
//如果第一个元素就是最小元素,那么它就和自己交换。如果只剩下最后一个元素,就没必要排了,它就是最大的
//再次在剩下的元素中找最小的元素,将它与数组中的第二个元素交换。如此往复,直到将整个数组排序。
public static void selectSort(int[] arr) {
//数组是空或者数组里面只有一个元素,没必要排序
if (null == arr || arr.length < 2)
return;
//最后一个元素没必要再给它排序,因为它就是最大的
for (int i = 0; i < arr.length - 1; i++) {
//初始的时候就认为最小值索引指针在i位置,最小值就是arr[i]
int minIndex = i;
//在I后面[i+1,arr.length-1]区间找最小的值
for (int j = i + 1; j < arr.length; j++) {
//找到就把最小值索引更新,找不到最小值索引还在原位
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
//将i位置上的元素值和最小索引位置的元素值交换
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
-
堆排序
相关知识:完全二叉树:叶子结点只可能在层次最大的两层上出现.,并且最下面一层节点都集中在该层最左边的若干位置的二叉树。堆是具有如下性质的二叉树:每个结点的值都小于(大于)或等于其左右孩子结点的值,称为小(大)顶堆。
Ki<=K2i; Ki<=K2i+1
<<左移运算符,num << 1,相当于num乘以2
>>右移运算符,num >> 1,相当于num除以2
基本思路:对一组待排序记录的键值,首先把它们按照堆的定义排成一个堆,称这一过程为建堆。首先找到最小(大)键值,然后将最小(大)键值取出,用剩下的键值再建堆,取得次小(大)的键值。如此反复进行,直到得到最大(小)键值,从而将全部键值排好序为止。
堆排序分为三个过程:
1. 建堆:从一个数组顺序读取元素,建立一个堆(完全二叉树)
2. 初始化:将堆进行调整,使得堆顶为最大(最大堆)或者最小(最小堆)的元素
3. 维护:将堆顶元素出堆后,需要将堆的最后一个节点补充到堆顶,因为这样破坏了堆的秩序,所以需要进行维护。
public class HeapSort{
public static int[] sort=new int[] {1,0,10,20,3,5,6,4,9,8,12,17,34,11};
public static void main(String[] args){
buildMaxHeapify(sort);
heapSort(sort);
}
public static void buildMaxHeapify(int[] arr){
int startIndex=getParentIndex(arr.length-1);
for(int i=startIndex;i>=0;i--){
maxHeapify(arr,arr.length,i);
}
}
public static void maxHeapify(int[] arr,int heapSize,int index){
int left=getChildLeftIndex(index);
int right=getChildRightIndex(index);
int largest=index;
if(left<heapSize && arr[index] < arr[left]){
largest=left;
}
if(right<heapSize&& arr[largest] < arr[right]{
largest=right;
}
//得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整
if(largest!=index){
int temp=arr[index];
arr[index]=arr[largest];
arr[largest]=temp;
maxHeapify(arr,heapSize,largest);
}
}
//排序,最大值放在末尾
private static void heapSort(int arr[]){
for(int i=arr.length-1;i>0;i--){
int temp=arr[0];
arr[0]=arr[i];
arr[i]=temp;
maxHeapify(arr,i,0);
}
}
//父结点位置
private static int getParentIndex(int current){
return (current-1) >> 1;
}
//左子结点位置
private static int getChildLeftIndex(int current){
return (current << 1)+1;
}
//右子结点位置
private static int getChildRightIndex(int current){
return (current << 1)+2;
}
}
其他排序
-
归并排序
基本思路:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
public class Sort{
public static void Merge(int arr[],int p,int q,int r){
int i,j,k,n1,n2;
n1=q-p+1;
n2=r-q;
int[] L=new int[n1];
int[] R=new int[n2];
for(i=0,k=p;i<n1;i++,k++)
L[i]=array[k];
for(j=0,k=q+1;j<n2;j++,k++)
R[j]=array[k];
for(k=p,i=0,j=0;i<n1 && j<n2;k++){
if(L[i]<R[j]){
arr[k]=L[i++];
}else{
arr[k]=R[j++];
}
}
while(i<n1){
arr[k++]=L[i++]
}
while(j<n2){
arr[k++]=R[j++]
}
}
public static vois MergeSort(int arr[],int p,int r){
if(p<r){
int q=(p+r)/2;
MergeSort(arr,p,q);
MergeSort(arr,q+1,r);
Merge(arr,p,q,r);
}
}
public Static void main(String[] args){
int i=0;
int a[]={5,4,9,8,7,6,0,1,3,2};
int len=a.length;
MergeSort(a,0,len-1);
}
}
-
计数排序
基本思路:对每一个输入的元素arr[i],确定小于 arr[i] 的元素个数。
所以可以直接把 arr[i] 放到它输出数组中的位置上。假设有5个数小于 arr[i],所以 arr[i] 应该放在数组的第6个位置上。
算法流程(1)
需要三个数组:
//待排序数组 int[] arr = new int[]{4,3,6,3,5,1};
//辅助计数数组 int[] help = new int[max - min + 1]; //该数组大小为待排序数组中的最大值减最小值+1
//输出数组 int[] res = new int[arr.length];
1.求出待排序数组的最大值max=6, 最小值min=1
2.实例化辅助计数数组help,help数组中每个下标对应arr中的一个元素,help用来记录每个元素出现的次数
3.计算 arr 中每个元素在help中的位置 position = arr[i] - min,此时 help = [1,0,2,1,1,1]; (3出现了两次,2未出现)
4.根据 help 数组求得排序后的数组,此时 res = [1,3,3,4,5,6]
public static int[] countSort1(int[] arr){
if (arr == null || arr.length == 0) {
return null;
}
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
//找出数组中的最大最小值
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
int help[] = new int[max];
//找出每个数字出现的次数
for(int i = 0; i < arr.length; i++){
int mapPos = arr[i] - min;
help[mapPos]++;
}
int index = 0;
for(int i = 0; i < help.length; i++){
while(help[i]-- > 0){
arr[index++] = i+min;
}
}
return arr;
}
算法流程(2)
需要三个数组:
//待排序数组 int[] arr = new int[]{4,3,6,3,5,1};
//辅助计数数组 int[] help = new int[max - min + 1]; //该数组大小为待排序数组中的最大值减最小值+1
//输出数组 int[] res = new int[arr.length];
1.求出待排序数组的最大值max=6, 最小值min=1
2.实例化辅助计数数组help,help用来记录每个元素之前出现的元素个数
3.计算 arr 每个数字应该在排序后数组中应该处于的位置,此时 help = [1,1,4,5,6,7];
4.根据 help 数组求得排序后的数组,此时 res = [1,3,3,4,5,6]
public static int[] countSort2(int[] arr){
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
//找出数组中的最大最小值
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
int[] help = new int[max - min + 1];
//找出每个数字出现的次数
for(int i = 0; i < arr.length; i++){
int mapPos = arr[i] - min;
help[mapPos]++;
}
//计算每个数字应该在排序后数组中应该处于的位置
for(int i = 1; i < help.length; i++){
help[i] = help[i-1] + help[i];
}
//根据help数组进行排序
int res[] = new int[arr.length];
for(int i = 0; i < arr.length; i++){
int post = --help[arr[i] - min];
res[post] = arr[i];
}
return res;
}
-
基数排序
基本思路:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
public class RadixSort {
/*
* 获取数组a中最大值
*
* 参数说明:
* a -- 数组
* n -- 数组长度
*/
private static int getMax(int[] a) {
int max;
max = a[0];
for (int i = 1; i < a.length; i++)
if (a[i] > max)
max = a[i];
return max;
}
/*
* 对数组按照"某个位数"进行排序(桶排序)
*
* 参数说明:
* a -- 数组
* exp -- 指数。对数组a按照该指数进行排序。
*
* 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
* (01) 当exp=1表示按照"个位"对数组a进行排序
* (02) 当exp=10表示按照"十位"对数组a进行排序
* (03) 当exp=100表示按照"百位"对数组a进行排序
* ...
*/
private static void countSort(int[] a, int exp) {
//int output[a.length]; // 存储"被排序数据"的临时数组
int[] output = new int[a.length]; // 存储"被排序数据"的临时数组
int[] buckets = new int[10];
// 将数据出现的次数存储在buckets[]中
for (int i = 0; i < a.length; i++)
buckets[ (a[i]/exp)%10 ]++;
// 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。
for (int i = 1; i < 10; i++)
buckets[i] += buckets[i - 1];
// 将数据存储到临时数组output[]中
for (int i = a.length - 1; i >= 0; i--) {
output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];
buckets[ (a[i]/exp)%10 ]--;
}
// 将排序好的数据赋值给a[]
for (int i = 0; i < a.length; i++)
a[i] = output[i];
output = null;
buckets = null;
}
/*
* 基数排序
*
* 参数说明:
* a -- 数组
*/
public static void radixSort(int[] a) {
int exp; // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
int max = getMax(a); // 数组a中的最大值
// 从个位开始,对数组a按"指数"进行排序
for (exp = 1; max/exp > 0; exp *= 10)
countSort(a, exp);
}
public static void main(String[] args) {
int i;
int a[] = {53, 3, 542, 748, 14, 214, 154, 63, 616};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
radixSort(a); // 基数排序
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
}
}
-
桶排序
桶排序可用于最大最小值相差较大的数据情况,比如[9012,19702,39867,68957,83556,102456]。
但桶排序要求数据的分布必须均匀,否则可能导致数据都集中到一个桶中。比如[104,150,123,132,20000], 这种数据会导致前4个数都集中到同一个桶中。导致桶排序失效。
基本思路:把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并。
计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。
算法流程:
1.找出待排序数组中的最大值max、最小值min
2.我们使用 动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1
3.遍历数组 arr,计算每个元素 arr[i] 放的桶
4.每个桶各自排序
5.遍历桶数组,把排序好的元素放进输出数组
//桶排序
public static void bucketSort(int[] arr){
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
//桶数
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
//将每个元素放入桶
for(int i = 0; i < arr.length; i++){
int num = (arr[i] - min) / (arr.length);
bucketArr.get(num).add(arr[i]);
}
//对每个桶进行排序
for(int i = 0; i < bucketArr.size(); i++){
Collections.sort(bucketArr.get(i));
}
System.out.println(bucketArr.toString());
}
参考博客:
https://www.cnblogs.com/bjh1117/p/8335628.html
https://blog.csdn.net/zhangerqing/article/details/8831542
https://segmentfault.com/a/1190000013967025(希尔排序)
https://www.cnblogs.com/zer0Black/p/6169858.html(计数排序、桶排序)