百味皆苦 java后端开发攻城狮

算法排序

2019-07-26
百味皆苦

选择排序

原理

  • 每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录
  • 基于此思想的算法主要有简单选择排序、树型选择排序和堆排序。(这里只介绍常用的简单选择排序)

基本思想

  • 简单选择排序的基本思想:给定数组:int[] arr={里面n个数据};
  • 第1趟排序,在待排序数据arr[1]~arr[n]中选出最小的数据,将它与arrr[1]交换;
  • 第2趟,在待排序数据arr[2]~arr[n]中选出最小的数据,将它与r[2]交换;
  • 以此类推,第i趟在待排序数据arr[i]~arr[n]中选出最小的数据,将它与r[i]交换,直到全部排序完成

举例

  • 数组 int[] arr={5,2,8,4,9,1};

  • 第一趟排序: 原始数据:5 2 8 4 9 1

    最小数据1,把1放在首位,也就是1和5互换位置,

    排序结果:1 2 8 4 9 5

  • 第二趟排序:

    第1以外的数据{2 8 4 9 5}进行比较,2最小,

    排序结果:1 2 8 4 9 5

  • 每一趟排序获得最小数的方法:for循环进行比较,定义一个第三个变量temp,首先前两个数比较,把较小的数放在temp中,然后用temp再去跟剩下的数据比较,如果出现比temp小的数据,就用它代替temp中原有的数据

代码实现

//选择排序
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr={1,3,2,45,65,33,12};
        System.out.println("交换之前:");
        for(int num:arr){
            System.out.print(num+" ");
        }        
        //选择排序的优化
        for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序
            int k = i;
            for(int j = k + 1; j < arr.length; j++){// 选最小的记录
                if(arr[j] < arr[k]){ 
                    k = j; //记下目前找到的最小值所在的位置
                }
            }
            //在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换
            if(i != k){  //交换a[i]和a[k]
                int temp = arr[i];
                arr[i] = arr[k];
                arr[k] = temp;
            }    
        }
        System.out.println();
        System.out.println("交换后:");
        for(int num:arr){
            System.out.print(num+" ");
        }
    }

}

时间复杂度

  • 简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 N 个元素,则比较次数永远都是N (N - 1) / 2。而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0。当序列反序时,移动次数最多,为3N (N - 1) / 2。
  • 简单排序的时间复杂度为 O(N2)

冒泡排序

  • 相邻元素前后交换、把最大的排到最后
  • 时间复杂度 O(n²)

思路1

  • 从左往右,两两比较,把最小的数往右移动

  • 假如有几个数字int score[] = {67, 69, 75, 88}; 按照从大到小排序
  • score[j] 和 score[j+1] 比较 如果 前者比后者小,把前者和后者调换顺序,两两调换后一轮下来 最小的会被排到最后去
  • 每一轮j都从0开始,当i轮排序,就有最后面的i个数字因为他是最小的,所以后面的每轮都不用理他了,也就是 score.length-1-i 往后的数不用管了,如上,第一轮有4个数字 i为0 ,那么score.length-1-i 为3,也就是下标是3以后的可以不用管,3往后没有数字,所以第一轮所有的数字都要参加比较,第二轮I=1 score.length-1-i 为2 也就是说 下标2后面的 下标为3的数字不用比了,因为两两比较后,67会到 score[3]
score[] = {67, 69, 75, 88}

第一次:67<69--->{69,67,75,88}--->67<75--->{69,75,67,88}-->67<88-->{69,75,88,67}

第二次:69<75-->{75,69,88,67}-->69<88-->{75,88,69,67}

第三次:75<88-->{88,75,69,67}
for(int i =0;i < score.length - 1;i++)
        {
            for(int j = 0;j <  score.length - 1-i;j++)// j开始等于0,
            {
                if(score[j] < score[j+1])
                {
                    int temp = score[j];
                    score[j] = score[j+1];
                    score[j+1] = temp;
                }
            }
        }

思路2

  • 用88 和 75 比较,在和69 比较 在和 67 比较,发现88是最大的,吧他排到第一位(index=0的位置),然后i=1,也就是第二轮,就不用看下标为0的88了因为他是老大,然后接着比较。
  • 从右往左,两两比较,把最大的数往左移动
score[] = {67, 69, 75, 88}

第一次:75<88-->{67,69,88,75}-->69<88-->{67,88,69,75}-->67<88-->{88,67,69,75}

第二次:69<75-->{88,67,75,69}-->67<75-->{88,75,67,69}

第三次:67<69-->{88,75,69,67}
for(int i =0;i < score.length - 1;i++)
        {
            for(int j = (score.length - 2);j >= i;j--)
            {
                if(score[j] < score[j+1])
                {
                    int temp = score[j];
                    score[j] = score[j+1];
                    score[j+1] = temp;
                }
            }
        }

插入排序

原理

  • 插入排序是基于比较的排序。所谓的基于比较,就是通过比较数组中的元素,看谁大谁小,根据结果来调整元素的位置。
  • 有两种基本的操作:①比较操作; ②交换操作
  • 对于交换操作,可以优化成移动操作,即不直接进行两个元素的交换,还是用一个枢轴元素(tmp)将当前元素先保存起来,然后执行移动操作,待确定了最终位置后,再将当前元素放入合适的位置。因为,交换操作需要三次赋值,而移动操作只需要一次赋值

分析

  • 插入排序算法有种递归的思想在里面,它由N-1趟排序组成。初始时,只考虑数组下标0处的元素,只有一个元素,显然是有序的
  • 然后第一趟 对下标 1 处的元素进行排序,保证数组[0,1]上的元素有序;
  • 第二趟 对下标 2 处的元素进行排序,保证数组[0,2]上的元素有序;
  • 第N-1趟对下标 N-1 处的元素进行排序,保证数组[0,N-1]上的元素有序,也就是整个数组有序了
  • 它的递归思想就体现在:当对位置 i 处的元素进行排序时,[0,i-1]上的元素一定是已经有序的了

举例

arr = {34,8,64,51};

第一次:p=1,tmp=8-->j=1,34>8-->{34,34,64,51}-->j=0-->{8,34,64,51}

第二次:p=2,tmp=64-->j=2,34<64-->{8,34,64,51}

第三次:p=3,tmp=51-->j=3,64>51-->{8,34,64,64}-->j=2,51>34-->{8,34,51,64}

实现

public class InsertSort{
    
    public static <T extends Comparable<? super T>> void insertSort(T[] a){
        for(int p = 1; p < a.length; p++)
        {
            T tmp = a[p];//保存当前位置p的元素,其中[0,p-1]已经有序
            int j;
            //for(j = p; j > 0 && tmp<(a[j-1]); j--)
            for(j = p; j > 0 && tmp.compareTo(a[j-1]) < 0; j--)
            {
                    a[j] = a[j-1];//后移一位
            }
            a[j] = tmp;//插入到合适的位置
        }
    }
    
    //for test purpose
    public static void main(String[] args) {
        Integer[] arr = {34,8,64,51,32,21};
        insertSort(arr);
        for (Integer i : arr) {
            System.out.print(i + " ");
        }
    }
}

复杂度

  • ①插入排序的时间复杂度 就是判断比较次数有多少,而比较次数与 待排数组的初始顺序有关,当待排数组有序时,没有移动操作(第8行for不成立),此时复杂度为O(N),当待排数组是逆序时,比较次数达到最大–对于下标 i 处的元素,需要比较 i-1 次。总的比较次数:1+2+…+N-1 ,故时间复杂度为O(N^2)
  • ①可以看出,算法中只用到了一个临时变量(第6行),故空间复杂度为O(1)

快速排序

原理

  • 快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。
  • 这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了

举例

{6  1  2  7  9  3  4  5 10  8}设基准数为6

这里可以用两个变量i和j,分别指向序列最左边和最右边,j向左移动寻找小于基准数的数,i向右移动寻找大于基准数的数。

首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要,j停在5,i停在7,交换,{6  1  2  5  9  3  4  7 10  8}

继续寻找,j停在4,i停在9,交换,{6  1  2  5  4  3  9  7 10  8}

继续,i,j都停在3,此时和基准数交换,{3  1  2  5  4  6  9  7 10  8},6左边全部≤6,右边全部≥6

在6的左边和右边采用同样的方法,递归,最终{1  2  3  4  5  6  7  8 9  10}

实现

public class QuickSort{
    
    public static void quickSort(Integer[] a,int left,int right)
    {
            int i,j,t,temp; 
            if(left>right) 
               return; 

            temp=a[left]; //temp中存的就是基准数 
            i=left; 
            j=right; 
            while(i!=j) 
            { 
                   //顺序很重要,要先从右边开始找 
                   while(a[j]>=temp && i<j) 
                            j--; 
                   //再找右边的 
                   while(a[i]<=temp && i<j) 
                            i++; 
                   //交换两个数在数组中的位置 
                   if(i<j) 
                   { 
                            t=a[i]; 
                            a[i]=a[j]; 
                            a[j]=t; 
                   } 
            } 
            //最终将基准数归位 
            a[left]=a[i]; 
            a[i]=temp; 

            quickSort(a,left,i-1);//继续处理左边的,这里是一个递归的过程 
            quickSort(a,i+1,right);//继续处理右边的 ,这里是一个递归的过程 
    }
    
    //for test purpose
    public static void main(String[] args) {
        Integer[] arr = {6,1,2,7,9,3,4,5,10,8};
        quickSort(arr,0,arr.length-1); //快速排序调用 
        for (Integer i : arr) {
            System.out.print(i + " ");
        }
    }
}

复杂度

  • 快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。

归并排序

原理

  • 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)

举例

实现

public class MergeSort {
    public static void main(String []args){
        int []arr = {9,8,7,6,5,4,3,2,1};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void sort(int []arr){
        int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
        sort(arr,0,arr.length-1,temp);
    }
    private static void sort(int[] arr,int left,int right,int []temp){
        if(left<right){
            int mid = (left+right)/2;
            sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
            sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
            merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
        }
    }
    private static void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left;//左序列指针
        int j = mid+1;//右序列指针
        int t = 0;//临时数组指针
        while (i<=mid && j<=right){
            if(arr[i]<=arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        while(i<=mid){//将左边剩余元素填充进temp中
            temp[t++] = arr[i++];
        }
        while(j<=right){//将右序列剩余元素填充进temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        //将temp中的元素全部拷贝到原数组中
        while(left <= right){
            arr[left++] = temp[t++];
        }
    }
}

复杂度

  • 每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为 log2n
  • 总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

堆排序

  • 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

基本思想

  • 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

步骤

  • 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  • 将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;
  • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

举例

  • 步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
    4
   / \
  6   8
 / \
5   9
arr{4,6,8,5,9}
此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

    4
   / \
  9   8
 / \
5   6
arr{4,9,8,5,6}

找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换
    9
   / \
  4   8
 / \
5   6
arr{9,4,8,5,6}

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
    9
   / \
  6   8
 / \
5   4
arr{9,6,8,5,4}
此时,我们就将一个无需序列构造成了一个大顶堆。
  • 步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
    9
   / \
  6   8
 / \
5   4
将堆顶元素9和末尾元素4进行交换

    4
   / \
  6   8
 / \
5   9
重新调整结构,使其继续满足堆定义

    8
   / \
  6   4
 / \
5   9
再将堆顶元素8与末尾元素5进行交换,得到第二大元素8

    5
   / \
  6   4
 / \
8   9
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

    6
   / \
  5   4
 / \
8   9

    4
   / \
  5   6
 / \
8   9
arr{4,5,6,8,9}

实现

package sortdemo;

import java.util.Arrays;

/**
 * Created by chengxiao on 2016/12/17.
 * 堆排序demo
 */
public class HeapSort {
    public static void main(String []args){
        int []arr = {9,8,7,6,5,4,3,2,1};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void sort(int []arr){
        //1.构建大顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j=arr.length-1;j>0;j--){
            swap(arr,0,j);//将堆顶元素与末尾元素进行交换
            adjustHeap(arr,0,j);//重新对堆进行调整
        }

    }

    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int []arr,int i,int length){
        int temp = arr[i];//先取出当前元素i
        for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        arr[i] = temp;//将temp值放到最终的位置
    }

    /**
     * 交换元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

复杂度

  • 堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成
  • 构建初始堆经推导复杂度为O(n)
  • 交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。

计数排序

思路

  • 假设输入数组是A[1…n],则需要一个辅助数组C[0…K],一个输出数组B[1…n]。其中k代表输入数组中的最大值,n代表输入数组的长度
  • 1、初始化辅助数组。
  • 2、循环遍历每一个输入元素,如果一个输入元素为i,则辅助数组中相应的C[i]的值加1。执行完毕之后。数组C中存储的就是各个键值在输入数组中出现的次数。
  • 3、再通过加总计算确定对于从1到k,有多少个输入元素是小于等于k的。将结果赋值到数组C中。
  • 4、循环将A[J]放到它在输出数组的正确位置上。对于一个值来说,C[A[J]]的值就是它在输出数组B中的正确位置。

案例

a{2,5,3,0,2,3,0,3},k=5,n=8

c{0,0,0,0,0,0}
  0 1 2 3 4 5 
  
初始化C数组
c{2,0,2,3,0,1}
  0 1 2 3 4 5
  
修改c数组,每个索引位置处的值为前i项合
c{2,2,4,7,7,8}
  0 1 2 3 4 5

构建b数组
a[0]=2,c[2]=4,b[4]=2
b{0,0,0,0,2,0,0,0}
  0 1 2 3 4 5 6 7
c{2,2,3,7,7,8}
  0 1 2 3 4 5

a[1]=5,c[5]=8,b[8]=5
b{0,0,0,0,2,0,0,0,5}
  0 1 2 3 4 5 6 7 8
c{2,2,3,7,7,7}
  0 1 2 3 4 5
  
a[2]=3,c[3]=7,b[7]=3
b{0,0,0,0,2,0,0,3,5}
  0 1 2 3 4 5 6 7 8
c{2,2,3,6,7,7}
  0 1 2 3 4 5  
  
a[3]=0,c[0]=2,b[2]=0
b{0,0,0,0,2,0,0,3,5}
  0 1 2 3 4 5 6 7 8
c{1,2,3,6,7,7}
  0 1 2 3 4 5 

a[4]=2,c[2]=3,b[3]=2
b{0,0,0,2,2,0,0,3,5}
  0 1 2 3 4 5 6 7 8
c{1,2,2,6,7,7}
  0 1 2 3 4 5
  
a[5]=3,c[3]=6,b[6]=3
b{0,0,0,2,2,0,3,3,5}
  0 1 2 3 4 5 6 7 8
c{1,2,3,5,7,7}
  0 1 2 3 4 5
  
a[6]=0,c[0]=1,b[1]=0
b{0,0,0,2,2,0,3,3,5}
  0 1 2 3 4 5 6 7 8
c{0,2,3,5,7,7}
  0 1 2 3 4 5
  
a[7]=3,c[3]=5,b[5]=3
b{0,0,0,2,2,3,3,3,5}
  0 1 2 3 4 5 6 7 8
c{0,2,3,4,7,7}
  0 1 2 3 4 5

实现

public class CountSort {

    private static int[] countSort(int[] array,int k)
    {
        int[] C=new int[k+1];//构造C数组
        int length=array.length,sum=0;//获取A数组大小用于构造B数组  
        int[] B=new int[length];//构造B数组
        for(int i=0;i<length;i++)
        {
            C[array[i]]+=1;// 统计A中各元素个数,存入C数组
        }
        for(int i=0;i<k+1;i++)//修改C数组
        {
            sum+=C[i];
            C[i]=sum;    
        }
        for(int i=length-1;i>=0;i--)//遍历A数组,构造B数组
        {
            
            B[C[array[i]]-1]=array[i];//将A中该元素放到排序后数组B中指定的位置
            C[array[i]]--;//将C中该元素-1,方便存放下一个同样大小的元素
            
        }
        return B;//将排序好的数组返回,完成排序
        
    }
    public static void main(String[] args)
    {
        int[] A=new int[]{2,5,3,0,2,3,0,3};
        int[] B=countSort(A, 5);
        for(int i=0;i<A.length;i++)
        {
            System.out.println((i+1)+"th:"+B[i]);
        }
    }
}

桶排序

原理

  • 桶排序即Bucket Sort,也称箱排序。其基本思想是将待排序数组分配到若干个桶内,然后每个桶内再各自进行排序,桶内的排序可以使用不同的算法,比如插入排序或快速排序,属于分治法。每个桶执行完排序后,最后依次将每个桶内的有序序列拿出来,即得到完整的排序结果。
  • 简单来看,桶排序的分治涉及到三部分:分、治、合。分,即将序列分成m个小序列;治,即对每个桶内的元素进行排序;合,即将每个桶合并到一起。

步骤

  • 根据序列大小范围划分m个大小相同的区间,每个区间即是一个桶。
  • 将待排序的n个元素分发到对应区间的桶中,即是分操作。
  • 对每个桶包含的元素进行排序,可以使用快速排序或其他排序,即是治操作。
  • 每个桶都是有序序列,按桶顺序依次取出每个桶的元素,得到最终完整的有序数组,即是合操作。

举例

arr{4, 7, 9, 13, 18, 1, 19, 11, 6, 15}

①:首先先定义桶的数量及区间,因为待排序数组的最大元素与最小元素分别为19和1,那么总的范围区间可定义为[0,19],假设用4个桶,则桶的区间分别为桶1[0,4],桶2[5,9],桶3[10,14],桶4[15,19]。

arr第一个元素为4,放入第一个桶
最终结果
桶1:1--》4
桶2:6--》7--》9
桶3:11--》13
桶4:15--》18--》19

现在每个桶都是一个有序序列,最后要执行合并操作,即按桶顺序依次取出每个桶的元素,最终完成整个序列的排序。
arr{1,4,6,7,9,11,13,15,18,19}

实现

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
 
public class Main {
 
    public static void main(String[] args) {
        // 输入元素均在 [0, 10) 这个区间内
        float[] arr = new float[] { 0.12f, 2.2f, 8.8f, 7.6f, 7.2f, 6.3f, 9.0f, 1.6f, 5.6f, 2.4f };
        bucketSort(arr);
        printArray(arr);
    }
 
    public static void bucketSort(float[] arr) {
        // 新建一个桶的集合
        ArrayList<LinkedList<Float>> buckets = new ArrayList<LinkedList<Float>>();
        for (int i = 0; i < 10; i++) {
            // 新建一个桶,并将其添加到桶的集合中去。
            // 由于桶内元素会频繁的插入,所以选择 LinkedList 作为桶的数据结构
            buckets.add(new LinkedList<Float>());
        }
        // 将输入数据全部放入桶中并完成排序
        for (float data : arr) {
            int index = getBucketIndex(data);
            insertSort(buckets.get(index), data);
        }
        // 将桶中元素全部取出来并放入 arr 中输出
        int index = 0;
        for (LinkedList<Float> bucket : buckets) {
            for (Float data : bucket) {
                arr[index++] = data;
            }
        }
    }
 
    /**
     * 计算得到输入元素应该放到哪个桶内
     */
    public static int getBucketIndex(float data) {
        // 这里例子写的比较简单,仅使用浮点数的整数部分作为其桶的索引值
        // 实际开发中需要根据场景具体设计
        return (int) data;
    }
 
    /**
     * 我们选择插入排序作为桶内元素排序的方法 每当有一个新元素到来时,我们都调用该方法将其插入到恰当的位置
     */
    public static void insertSort(List<Float> bucket, float data) {
        ListIterator<Float> it = bucket.listIterator();
        boolean insertFlag = true;
        while (it.hasNext()) {
            if (data <= it.next()) {
                it.previous(); // 把迭代器的位置偏移回上一个位置
                it.add(data); // 把数据插入到迭代器的当前位置
                insertFlag = false;
                break;
            }
        }
        if (insertFlag) {
            bucket.add(data); // 否则把数据插入到链表末端
        }
    }
 
    public static void printArray(float[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + ", ");
        }
        System.out.println();
    }
 
}

基数排序

原理

  • 将整数按位数切割成不同的数字,然后按每个位数分别比较。
  • 具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列

举例

arr{53, 3, 542, 748, 14, 214, 154, 63, 616}

初始状态	按个位排序	按十位排序	按百位排序
053			542			003			003
542			053			014			014
003			003			214			053
748			063			616			063
014			014			542			154
214			214			748			214
154			154			053			542
063			616			154			616
616			748			063			748

实现

/**
 * 基数排序:Java
 *
 * @author skywang
 * @date 2014/03/15
 */

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");
    }
}

二分查找

原理

  • 有序的序列,每次都是以序列的中间位置的数来与待查找的关键字进行比较,每次缩小一半的查找范围,直到匹配成功。

递归实现

	/**
	 * 使用递归的二分查找
	 *title:recursionBinarySearch
	 *@param arr 有序数组
	 *@param key 待查找关键字
	 *@return 找到的位置
	 */
	public static int recursionBinarySearch(int[] arr,int key,int low,int high){
		
		if(key < arr[low] || key > arr[high] || low > high){
			return -1;				
		}
		
		int middle = (low + high) / 2;			//初始中间位置
		if(arr[middle] > key){
			//比关键字大则关键字在左区域
			return recursionBinarySearch(arr, key, low, middle - 1);
		}else if(arr[middle] < key){
			//比关键字小则关键字在右区域
			return recursionBinarySearch(arr, key, middle + 1, high);
		}else {
			return middle;
		}	
		
	}

非递归实现

	/**
	 * 不使用递归的二分查找
	 *title:commonBinarySearch
	 *@param arr
	 *@param key
	 *@return 关键字位置
	 */
	public static int commonBinarySearch(int[] arr,int key){
		int low = 0;
		int high = arr.length - 1;
		int middle = 0;			//定义middle
		
		if(key < arr[low] || key > arr[high] || low > high){
			return -1;				
		}
		
		while(low <= high){
			middle = (low + high) / 2;
			if(arr[middle] > key){
				//比关键字大则关键字在左区域
				high = middle - 1;
			}else if(arr[middle] < key){
				//比关键字小则关键字在右区域
				low = middle + 1;
			}else{
				return middle;
			}
		}
		
		return -1;		//最后仍然没有找到,则返回-1
	}

	public static void main(String[] args) {
 
		int[] arr = {1,3,5,7,9,11};
		int key = 4;
		//int position = recursionBinarySearch(arr,key,0,arr.length - 1);
		
		int position = commonBinarySearch(arr, key);
 
               if(position == -1){
			System.out.println("查找的是"+key+",序列中没有该数!");
		}else{
			System.out.println("查找的是"+key+",找到位置为:"+position);
		}
		
	}

十大排序动画演示

动画


上一篇 MK电商项目

Comments

Content