排序算法内含各种算法及优化(冒泡排序、直接插入排序、希尔排序、快速排序、堆排序、归并排序、基数排序、计数排序、桶排序)

最近因为面试,都没好好学习算法,将冒泡排序、直接插入排序、希尔排序、快速排序、堆排序、归并排序、基数排序、计数排序、桶排序写了一下

一、冒泡排序

/**
 * Created by april on 2018/8/3.
 * 冒泡排序O(n的平方)
 */
public class Bubble_sort
{
    public static void main(String[] args){
        int [] arr = {1,1,2,0,9,3,12,7,8,3,4,65,22};
        BubbleSort_3(arr,arr.length);
        for(int i: arr){
            System.out.print(i+",");
        }

    }
    public static void BubbleSort_1(int[] a ,int n){
        for(int i=0; i<n; i++){//n次排序
            for(int j=i+1; j<n;j++){
                if(a[i]>a[j]){
                    int temp = a[i];
                    a[i] = a[j];
                    a[j] = temp;
                }

            }
        }
    }
    /*优化1:
    1、交换n遍,每一遍相邻交换,则把最大的放在了后面
    2、设置flag,排序一遍减去尾边界
    * */
    public static void BubbleSort_2(int[] a, int n){
        boolean flag = true;
        int k = n;
        while(flag){
            flag = false;
            for (int j=0;j<n-1;j++){
                if(a[j]>a[j+1]){//判断相邻结果的大小
                    int temp;
                    temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                    flag = true;
                }
            }
            k--;//减少一次排序的尾边界
        }
    }
    /*优化2:
    现在有一个包含1000个数的数组,仅前面100个无序,后面900个都已排好序且都大于前面100个数字
    1、只需要有一次比较后面的900个数据,之后就会设置尾边界,保证后面的900个数据不再被排序
    2、再按BubbleSort_2进行排序
    * */
    public static void BubbleSort_3(int[] a, int n){
        int k;
        int flag = n;
        while(flag > 0 ){//排序未结束的标志
            k = flag;// k存遍历的尾边界
            flag = 0;
            for(int j = 0; j< k-1; j++){
                if(a[j]>a[j+1]){
                    int temp;
                    temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                    flag = j+1;//重新设置尾边界
                }
            }
        }
    }
}

二、直接插入排序

/**
 * Created by april on 2018/8/3.
 * 直接插入排序
 * O(n的平方)
 */
public class Straight_insertion
{
    public static void main(String[] args){
        int[] arr = {1,1,2,0,9,3,12, 7, 8, 3, 4, 65, 22};
        Straight_insertion.insertSort_3(arr,arr.length);
        for(int i : arr){
            System.out.print(i+",");
        }
    }
    /*
    找关键字
    将a[i]并入当前的有序区a[0...i-1]中形成a[0...i]的有序区间
    i++并重复第二步直到i=n-1,排序完成
    * */
    public static void insertSort_1(int[] a, int n){
        int i,j;
        for(i=1; i<n; i++ ){//依次把a[1]到a[n-1]插入到前面有序序列中
            //将要插入的a[i]与前面的比较,0-i-1比较知道找到比i大
            for(j = 0; j<i; j++){
                if(a[j]>a[i]) break;//如果a[j]比a[i]大,求得a[i]的位置,应该放在a[j]前
            }
            //将a[i]插入到a[j]的位置
            int temp = a[i];//找到a[i]的数据
            for(int k = i-1; k>=j;k--){//a[j]到a[i-1]从后往前后移
                    a[k+1] = a[k];
            }
            a[j] = temp;//将a[i]插入到a[j]前
        }
    }
    /*
    优化1:
    从第一个开始比较,a[i]只与a[i-1]比较,如果a[i-1]<a[i],a[0...i]有序
    若a[i-1]>a[i],边比较边移动,j=i-1,将a[j]往后移动一边向前搜索是否还有比a[i]大的
    * */
    public static void insertSort_2(int[] a, int n){
        int i,j;
        for(i=1; i<n; i++){
            if(a[i]>a[i-1]){
                int temp = a[i];//保存要插入的数据
                for(j=i-1;j>=0&&a[j]>temp;j--){
                    a[j+1] = a[j];//将i-1往后移动
                }
                //插入数据
                a[j+1] = temp;
            }
        }
    }
    /*优化2:
       1、a[i]与前一个a[i-1]交换,并且如果比前面的小,一直和之前的交换
    * */
    public static void insertSort_3(int[] a, int n){
        int i,j;
        for(i=1;i<n;i++){
            for(j=i-1;j>=0 && a[j]>a[j+1]; j--){
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;

            }

        }
    }
}

三、希尔排序

/**
 * Created by april on 2018/8/8.
 * 希尔排序:插入排序,缩小增量排序,比O(n的平方小)
 * 注:希尔排序的增量序列选择与证明是难题
 *    建议的增量排序,希尔增量:gap = length/2,gap = gap/2
 * 思考: 先选择gap = length/2的增量,分为若干组进行排序
 *        继续gap = gap/2
 *        当gap=1,对整个序列进行一趟直接插入排序
 *
 */
public class Shell_sort
{
    public static void main(String[] args){
        int[] arr = {49,38,65,97,76,13,27,49,55,04};
        System.out.println(Arrays.toString(arr));
        move_sort(arr);
        System.out.println(Arrays.toString(arr));

    }
    public static void move_sort(int[] arr){
         for(int gap = arr.length/2;gap>0;gap/=2){//增量gap,最后的gap一定是1
             for(int i = gap;i<arr.length;i++){
                 int j = i;
                 int temp = arr[i];//先保存要插入的数据
                 //从前往后排序,所以前面是部分有序的,则先比较j与j-gap处的值,如果j处的值比j-gap处的值小,则前一直比较,a[j]=a[j-gap],
                 if(arr[j]<arr[j-gap]){
                     while(j-gap>=0&&temp<arr[j-gap]){
                         arr[j] = arr[j-gap];
                         j-=gap;//继续按gap的间隔比较值
                     }
                 }
                 arr[j] = temp;//比较到最后一个比temp值小的地方,则插入temp值
             }

         }
    }
}

四、快速排序

/**
 * Created by april on 2018/8/3.
 * 快速排序,不稳定,最差是O(n的地方),平均时间复杂度是O(nlogn),用主定理分析
 */
public class Quick_sort
{
    public static void main(String[] args){
//        int[] arr = {49,38,65,97,76,13,27,49};
//        Quick_sort.Quick_1(arr,0,arr.length-1);
//        for(int i:arr){
//            System.out.print(i+",");
//        }

        //输入链表 2 2 5 3 8 4 2 1
        ListNode head = new ListNode(2);
        ListNode l1 = new ListNode(2);
        ListNode l2 = new ListNode(5);
        ListNode l3 = new ListNode(3);
        ListNode l4 = new ListNode(8);
        ListNode l5 = new ListNode(4);
        ListNode l6 = new ListNode(2);
        ListNode l7 = new ListNode(1);
        //将链表连接起来
        head.next = l1;
        l1.next = l2;
        l2.next = l3;
        l3.next = l4;
        l4.next = l5;
        l5.next = l6;
        l6.next = l7;
        l7.next = null;

        ListNode p = head;
        while (p.next != null){
            System.out.print(p.val+" ");
            p = p.next;
        }
        //要得到最后一个数,所以需要单独打印
       System.out.println(p.val);
        ListNode begin = head, end = p;
        Quick_ListNode(begin,end);//第一个和后一个,要用于判断是否为空,还有链表中是否只有一个数
        p = head;
        while (p!=null){
            System.out.print(p.val+" ");
            p = p.next;
        }

    }
    
    
    /*一趟快排
    (1)先选取基准点key,一般选a[0],从后半部分开始扫描,若比key值大则继续往前移动,若比key值小,则交换a[low]和a[high],此时a[low]为key
    (2)在从前半部分开始扫描,若比Key值小则继续往后移动,若比key值大,则交换a[low]和a[high],因为之前的a[low]已经保存了上次的a[high],所以现在a[high]=现在a[low]
    (3)获取划分点
    * */
    public static int One_Quick(int[] a,int low, int high){
        int key=a[low];
        while(low<high){
            while(low<high&&a[high]>key)//while里必须加上low<high 否则high一直--
                high--;
            a[low] = a[high];
            while (low<high&&a[low]<=key)
                low++;
            a[high] = a[low];
        }
        //当low=high时
        a[high] = key;
        return high;//得到key值所在的点,获得划分点
    }
    //实现快排
    public static void Quick_1(int[] a, int low,int high){
        if(low>=high) return;
        //获取划分点
        int index = One_Quick(a,low,high);
        //对前半部分排序
        Quick_1(a,low,index-1);
        //对后半部分排序
        Quick_1(a,index+1,high);

    }
    
    
    /*单链表实现快排,思想还是把小于key值的放在左边,大于key值的放在右边
    1、两个指针,first和second,first从头开始,second从first.next开始,key值选为first;
    2、由second开始遍历,当second>key,则first->first.next,swap(first,second)   3、继续second->second.next,直到second到链表末尾
    4、交换头结点和first的值,完成一趟快排
    5、再使用递归完成快排
    * */
    public static void Quick_ListNode(ListNode begin,ListNode end){
        if(begin == null || end == null || begin==end){
            return;
        }
        ListNode first = begin;
        ListNode key = first;
        ListNode second  = begin.next;
        while(second != end.next && second != null){//当second遍历到链表末尾
            if(second.val>key.val){
                first=first.next;
                swap(first,second);
            }
            second = second.next;

        }
        if(begin != first)
        {
            swap(begin,first);
        }
        Quick_ListNode(begin,first);
        Quick_ListNode(first.next,end);

    }
    public static void swap(ListNode first,ListNode second){
        int temp;
        temp = first.val;
        first.val = second.val;
        second.val = temp;
    }

}

五、堆排序

/**
 * Created by april on 2018/8/7.
 * 堆排序
 * 思想:
 * 将待排序序列构造一个大顶堆,整个序列的最大值就是堆顶的根节点
 * 将其与末尾元素进行交换,此时末尾就为最大值
 * 将剩余n-1个元素重新构造成一个堆,就会得到n个元素的次小值
 * 反复得到一个有序序列
 */
public class Heap_sort
{

    public static void main(String[] args){
        int[] arr = {70,60,12,40,30,8,10};
        heap_sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void heap_sort(int[] arr){
        //构建大顶堆
        for(int i = arr.length/2-1; i>=0; i--){
            adjust_heap(arr,i,arr.length);//从第一个非叶子结点从下至上,从右到左调整结构
        }
        //调整堆结构,交换堆顶元素与末尾元素
        for(int j = arr.length-1; j>0;j--){
            swap(arr,0,j);
            adjust_heap(arr,0,j);//重新对堆进行调整
        }
    }
    //调整堆,在大顶堆已构建的情况下
    public static void adjust_heap(int[] arr, int i, int length){
        int temp = arr[i];
        for(int k = i*2+1;k<length; k = k*2+1){//从结点的左子节点开始
            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,则K指向右子结点
                k++;//k+1就从左子结点指向右子结点
            }
            if(arr[k]>temp){//如果子节点大于父节点,将子节点复制给右节点(不用交换),因为事先已经将父节点取出来了
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        arr[i] = temp;//将temp值放在最终的位置
    }
    //交换元素
    public static void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

六、归并排序

/**
 * Created by april on 2018/8/4.
 * 归并排序 O(nlogn)
 * 将两个已排序的表合并成一个表
 * 先递归拆分序列,再归并
 */
public class Merge_sort
{

    public static void main(String[] args){
        //数组
//        int[] arr = {49,38,65,97,76,13,27};
//        Merge_sort.sort(arr);
//        System.out.println(Arrays.toString(arr));
        //链表

        ListNode head = new ListNode(0);
        ListNode l1 = new ListNode(2);
        ListNode l2 = new ListNode(5);
        ListNode l3 = new ListNode(3);
        ListNode l4 = new ListNode(8);
        ListNode l5 = new ListNode(4);
        ListNode l6 = new ListNode(2);
        ListNode l7 = new ListNode(1);
        //将链表连接起来
        head.next = l1;
        l1.next = l2;
        l2.next = l3;
        l3.next = l4;
        l4.next = l5;
        l5.next = l6;
        l6.next = l7;
        l7.next = null;

        ListNode p = head;
        while (p.next != null){
            System.out.print(p.val+" ");
            p = p.next;
        }
        //要得到最后一个数,所以需要单独打印
        System.out.print(p.val);
        System.out.println();

        new Merge_sort().merge_sort(head);
        p = head;
        while(p!=null){
            System.out.print(p.val+" ");
            p = p.next;
        }

    }
    /*一、基于数组实现,先把数组对半划分,再对半划分的数组排序,从而使左右两个子数组各自有序,最后再将两排序号的子数组进行归并成一个大的有序数组*/
    //排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
    public static void sort(int[] arr){
        int[] temp = new int[arr.length];
        mergeSort(arr,0,arr.length-1,temp);
    }
    //递归拆分、二路归并,
    // 决
    public static void mergeSort(int[] a, int first, int last, int[] temp){
        if(first < last){
            int mid = (first+last)/2;
            mergeSort(a,first,mid,temp);//左边有序
            mergeSort(a,mid+1,last,temp);//右边有序
            mergeArray(a,first,mid,last,temp);//再将两个有序序列合并
        }
    }
    //归并两个有序序列,a[first,mid]和a[mid+1,end]
    private static void mergeArray(int[] a, int first, int mid, int last, int[] temp){
        int i = first, j = mid+1;//设置两个数字的起始边界
        //int m = mid, n = last;//设置两个数组的结束边界
        int k=0;//temp的指针数组
        while(i <= mid && j <= last){
            if(a[i] <= a[j]){
                temp[k++] = a[i++];
            }else{
                temp[k++] = a[j++];
            }
        }
        while(i<=mid){
            temp[k++] = a[i++];
        }
        while (j<=last){
            temp[k++] = a[j++];
        }
        k = 0;
        while(first<=last){
            a[k++] = temp[k++];//将temp中的元素全部拷贝到原数组中,最后输出原数组
        }

    }


    /*二、基于链表实现
    * (1)合并:对于两个有序单链表的合并并返回合并之后的单链表头结点
    * (2)拆分,使用双指针法,p1和p2分别往后移动,p1移动一次,p2移动两次,当p2移动到尾结点时,p1就指向中间结点
    * */
    //拆分
    public ListNode merge_split(ListNode head){
        if(head == null) return head;
        ListNode p1 = head;
        ListNode p2 = head;
        while(p2.next != null && p2.next.next != null){//当p2的next为空的时候,p2为尾结点,且p2要移动两步
            p1 = p1.next;//p1走一步
            p2 = p2.next.next;//p2走两步
        }
        return p1;//此时p1为中间结点
    }
    //合并
    public ListNode merge(ListNode first,ListNode second){
        //
        ListNode dummyHead = new ListNode(0);
        ListNode curr = dummyHead;
        while(first != null && second != null){//比较两个子序列
            if(first.val <= second.val){
                curr.next = first;//放入较小的
                first  = first.next;
            }else{
                curr.next = second;
                second = second.next;
            }
            curr = curr.next;
        }
        curr.next = (first == null) ? second : first;
        return dummyHead.next;
    }
    //单链表的归并排序
    public ListNode merge_sort(ListNode head){
        //[i...middle]和[middle+1...n]
        if(head == null || head.next == null) return head;
        ListNode middle = merge_split(head);
        ListNode shalf = middle.next;//
        middle.next = null;//拆分链表

        return merge(merge_sort(head),merge_sort(shalf));
    }
}

七、基数排序

/**
 * Created by april on 2018/8/8.
 * 基数排序
 *
 * 基本概念:
 *  LSD:短的关键字被认为是小的,排在前面,然后相同长度的关键字再按照词典顺序或者数字大小等进行排序。比如1,2,3,4,5,6,7,8,9,10,11或者”b, c, d, e, f, g, h, i, j, ba” 。
 *  MSD:直接按照字典的顺序进行排序,对于字符串、单词或者是长度固定的整数排序比较合适。比如:1, 10, 2, 3, 4, 5, 6, 7, 8, 9和 “b, ba, c, d, e, f, g, h, i, j”。
 *
 *基数排序思想:从低位开始将待排序的数按照这一位的值放到相应的编号0~9的桶中,等到低位排完得到一个子序列,再将这个序列按照次低位的大小进入相应的桶中,一直排到最高位为止,数组排序完成
        举例:若数组为{73,22,93,43,55,14,28,65,39,81}
    用一个二维数组bucket[10][arr.length]来保存桶的结构,行为0~9的桶,列为桶里的个数(排序结果相同放在一个桶里)
                0    1    2    3    4    5   6    7    8   9
 1、按第一位排序
                0    1    2    3    4    5    6    7    8   9
                    81   22   73   14   55             28  39
                              93        65
                              43
 可得按个位排序的结果是{81,22,73,93,43,14,55,65,28,39}
 2、按第二位排序
                0    1    2    3    4    5    6    7    8   9
                    14   22   39   43   55    65   73   81  93
                         28
 取出排序结果{14,22,28,39,43,55,65,73,81,93}
 */
public class Radix_sort
{
    public static void main(String[] args){
        int[] arr = {73,22,93,43,55,14,28,65,39,81};
        System.out.println(Arrays.toString(arr));
        radix_sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    //要求比最大的值的位数还要大1位,如题目中d应该为100
    public static int  getMaxDigit(int[] arr){
        int d = 10;
        for(int i = 0; i<arr.length;i++){
            while(arr[i]>=d){
                d *= 10;
            }
        }
        return d;
    }
    public static void radix_sort(int[] arr){
        int d = getMaxDigit(arr);
        int n = 1;
        int k = 0;//保存每一次排序的结果,保存在原来arr数组
        int[][] bucket = new int[10][arr.length];//桶,存储排序结果
        int[] order = new int[arr.length];//存储桶的每列有多少个数,从0开始
        while(n<d){//n从个位开始
            for(int num : arr){//将原来数组里的每个数放在对应的桶里,从个位开始。。。。
                int digit = (num/n)%10;
                bucket[digit][order[digit]] = num;
                order[digit]++;
            }
            for(int i = 0;i<arr.length;i++){//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
                if(order[i]!=0){//桶里有数据就要取出来
                    for(int j = 0; j<order[i];j++){
                        arr[k] = bucket[i][j];//将每列的数取出来
                        k++;
                    }
                    order[i] = 0;//将order[i]重置为0,用于下一次排序
                }
            }
            n*=10;

            k = 0;//arr数组的计数重置为0
        }

    }
}

八、计数排序

/**
 * Created by april on 2018/8/8.
 * 计数排序:知道数组里有多少项小于或等于该元素,就能给出该元素在排序后的数组的位置(第3步就是做这件事情)
 * 例如A={2,5,3,0,2,3,0,3}
 * 1、原数组A: 0  1  2  3  4  5  6  7
          2  5  3  0  2  3  0  3
 * 2、数组里最大值为5,则初始化C的大小为6,原数组里有2个0,0个1,2个2,3个3,0个4,1个5.
        C:0  1  2  3  4  5
          2  0  2  3  0  1
 *3、将C中每个i位置的元素大小改为C数组前i项和
       C:0  1  2  3  4  5
         2  2  4  7  7  8
 *4、初始化和A一样大小的B用来存排序后的数组,倒序遍历A,为了稳定性(即原数组中A的三个3相对距离不变)
 (1)i=7时,a[i]=3,c[3]=7,b[7-1]=b[6]=3,c[3]=c[3]-1
       B: 0  1  2  3  4  5  6  7     C: 0  1  2  3  4  5
                            3           2  2  4  6  7  8
 (2)i=6时,a[i]=0,c[0]=2,b[2-1]=b[1]=0,c[0]=c[0]-1
       B: 0  1  2  3  4  5  6  7     C: 0  1  2  3  4  5
             0              3           1  2  4  6  7  8
 ......
 */
public class Count_sort
{
    public static void main(String[] args){
        int[] arr = {2,5,3,0,2,3,0,3};
        System.out.println(Arrays.toString(arr));
        int max = getMax(arr);
        int[] B = count_sort(arr,max);//用B存排序后的数组
        System.out.println(Arrays.toString(B));

    }
    //得到原数组里的最大值,用来构造C
    public static int getMax(int[] arr){
        int max = arr[0];
        for(int i=0;i<arr.length;i++){
            if(max<arr[i]){
                max = arr[i];
            }
        }
        return max;
    }
    public static int[] count_sort(int[] arr,int max){
        int[] C = new int[max+1];//最大值为max,则C的大小为max+1(~max)
        int[] B = new int[arr.length];
        int sum = 0;
        for(int i = 0;i<arr.length;i++){
            C[arr[i]]++;//统计arr的元素
        }
        for(int i = 0;i<C.length;i++){//修改C的元素大小,改为C数组前i项和
            sum += C[i];
            C[i] = sum;
        }
        for(int i = arr.length-1;i>=0;i--){//倒序遍历A
            B[C[arr[i]]-1] = arr[i];//B存排序后的数组
            C[arr[i]]--;
        }
        return B;
    }
}

九、桶排序

/**
 * Created by april on 2018/8/8.
 * 桶排序:O(N+n),空间换时间
 *
 *简单入门:
 * O(桶的个数+待排序的个数)
 *      先给一个数组(即为桶),全部赋值为0
 *     每出现一个值,对应数组就加1(即对应的桶里放一个小旗子)
 *     再依次判断数组里不为空的值,出现几次(有多少个小旗子)就打印几次
 *
 *桶排序:将[0,1)区间划分为n个相同的大小的子区间,这些子区间被称为桶
        然后将n个输入元素分别放入各自的桶中,因为输入时均匀独立的,一般不会有很多数同时落在一个桶中的情况
        要对各个桶中的数据进行排序,然后遍历各个桶,按照次序把各个桶中的元素列出来即可
 举例:
              A                    B
             78                    0
             17                    1 :12 :17
             39                    2 :21 :23 :26
             26                    3 :39
             72                    4
             94                    5
             21                    6 :68
             12                    7 :72 :78
             23                    8
             68                    9 :94
 */
public class Bucket_sort
{
    public static void main(String[] args){
        int[] arr = {78,17,39,26,72,94,21,12,23,68};
        System.out.println(Arrays.toString(arr));

        buck_sort(arr);
        System.out.println(Arrays.toString(arr));


        /*桶排序简单入门*/
//        int[] book  = new int[101];
//        for(int i = 0; i<=100; i++){
//            book[i] = 0;
//        }
//        for(int i = 0;i<arr.length;i++){
//            int temp = arr[i];
//            book[temp]++;
//        }
//        for(int i = 100;i>=0;i--){//由大到小输出
//            for(int k = 1;book[i]>=k;k++){//输出至少桶里为1个的数,桶里的数有几个就输出几次
//                System.out.print(i+" ");
//            }
        }
    public static void buck_sort(int[] arr){
        int bucknum = arr.length;
        List<ArrayList<Integer>> list = new ArrayList<>(bucknum);
        for(int i = 0; i<bucknum; i++)
            list.add(new ArrayList<Integer>());
        //确定元素的最值
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for(int a : arr){
            max = Math.max(max,a);
            min = Math.min(min,a);
        }
        //确定每个元素对应桶的编号并放进去
        for(int a : arr){
            int index = (int)((a - min)/(max+min+1.0)*arr.length);//加1为了防止程序抛出IndexOutOfBoundsEx
            list.get(index).add(a);
        }
        //桶内排序
        for(int i = 0; i<list.size();i++){
            Collections.sort(list.get(i));
        }
        //合并数据
        int k = 0;
        for(ArrayList<Integer> A : list){
            for (int a : A){
                arr[k++] = a;
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/languolan/article/details/81415266