Java中的插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序

这里的排序都以升序为例

1.插入排序

public static void insertSort(int[] array) {
        for (int bound = 1;bound<array.length;bound++) {
            int tmp = array[bound];
            int cur = bound-1;
            for (;cur>=0;cur--) {
                if (array[cur]>tmp) {
                    array[cur+1] = array[cur];

                }else {
                    break;
                }
            }
            array[cur+1] = tmp;
        }
    }

从bound号下标元素开始,用一个值tmp记住该元素值,因为自己不需要和自己比较,就从下标为1开始,用bound元素和前面元素进行比较,发现比bound元素大的,就把该元素向后移一位,直到找到比bound元素小的,就把tmp插入到该元素的后面。这要前面就是排好序的区间,后面就是待排区间。

时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定

2.希尔排序

public static void shellSort(int[] array) {
        int gap = (array.length)/2;
        while (gap>1) {
            insertSortGap(array,gap);
            gap = gap/2;
        }
        insertSortGap(array,1);
}

public static void insertSortGap(int[] array,int gap) {
        for (int bound = gap;bound<array.length;bound++) {
            int tmp = array[bound];
            int cur = bound-gap;
            for (;cur>=0;cur-=gap) {
                if (array[cur]>tmp) {
                    array[cur+gap] = array[cur];
                }else {
                    break;
                }

            }
            array[cur+gap] = tmp;

        }
}

希尔排序是插入排序的优化,就是分组后然后分别对每个组进行插入排序。分组呢就是指定一个数,每隔这个数取一个元素,作为一组,一般的,这个gap取length/2,length/4,length/8.。。。直到gap =1在处理一波。根据插入排序,每次都要从下标为1的元素开始,所以每组的下标为1的元素是原数组下标为gap的元素,然后原本对于插入排序要后移一位的,这里都要移gap位,其他基本都一样。

时间复杂度:O(n^1.3)
空间复杂度:O(1)
稳定性:不稳定

3.选择排序

public static void selectSort(int[] array) {
        for (int bound = 0; bound<array.length;bound++) {
            for (int cur = bound;cur<array.length;cur++) {
                if (array[cur]<array[bound]) {
                    swap(array,cur,bound);
                }
            }
        }
    }
 private static void swap(int[] array, int cur, int bound) {
        int tmp = array[cur];
        array[cur] = array[bound];
        array[bound] = tmp;
    }

选择排序是从0号下标开始,每次都从后面的元素中选出一个比当前元素小的进行交换,这样最小的就排再了最前面。前面是已排序区间,后面是待排序区间。

时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定

4.堆排序

   public static void heapSort(int[] array) {
        createHeap(array);
        int heapSize = array.length;
        for (int i = 0;i<array.length-1;i++) {
            swap(array,0,heapSize-1);
            heapSize--;
            shiftDown(array,heapSize,0);
        }
    }
    private static void createHeap(int[] array) {
        for (int i = (array.length-1-1)/2;i>=0;i--) {
            shiftDown(array,array.length,i);
        }
    }
private static void swap(int[] array, int cur, int bound) {
        int tmp = array[cur];
        array[cur] = array[bound];
        array[bound] = tmp;
    }
    private static void shiftDown(int[] array, int heapSize, int i) {
        int parent = i;
        int child = 2*parent+1;
        while (child<heapSize) {
            if (child+1<heapSize && array[child+1]>array[child]) {
                child = child+1;
            }
            if (array[child]>array[parent]) {
                swap(array,child,parent);
            }else {
                break;
            }
            parent = child;
            child = 2*parent+1;
        }

    }

先用向下调整构造大堆,用一个数表示当前堆得大小,然后进去循环,每次把堆顶元素和最后一个元素进行交换,然后把除最后一个元素外(就是对堆的大小进行减减操作)的堆进行向下调整使其重新变为大堆,这里只要循环length-1就行了,因为当堆剩下最后一个元素时就不需要进行任何操作了。
关于建堆操作,就从最后一个非叶子节点开始向下调整就行了。向下调整,从当前指定下标元素开始,为父节点,左孩子下标为乘以2加一,循环条件为左孩子小标小于当前堆的大小,如果右孩子也小于堆的大小并且比左孩子大,就把右孩子下标给左孩子,保证左孩子一定是最大的,然后和父节点比大小进行适当交换,如果符合大堆要求,直接break就行了,然后把孩子节点下标赋给父节点,新的孩子节点再再新的父节点基础上乘以2加一,进行下次循环。

时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定

5.冒泡排序

public static void bubbleSort(int[] array) {
        for (int bound = 0;bound<array.length;bound++) {
            for (int cur = array.length-1;cur>bound;cur--) {
                if (array[cur-1]>array[cur]) {
                    swap(array,cur-1,cur);
                }
            }
        }
    }

冒泡排序,从最后两个元素开始,每次不符合升序,就交换两个值,然后都往前移一位,再比较两个值,这样最小的就到最前面了。,注意cur的循环终止条件,是>,没有等于号。这样前面就是已排序区间,后面是待排序区间。

时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定

6.快速排序

 public static void quickSort(int[] array) {
        quickSortHelper(array,0,array.length-1);
    }

    private static void quickSortHelper(int[] array, int left, int right) {
        if (left>=right) {
            return;
        }
        int index = partition(array,left,right);
        quickSortHelper(array,left,index-1);
        quickSortHelper(array,index+1,right);


    }

    private static int partition(int[] array, int left, int right) {
        int baseValue = array[right];
        int i = left;
        int j = right;
        while (i<j) {
            while (i<j && array[i]<=baseValue) {
                i++;
            }
            while (i<j&&array[j]>=baseValue) {
                j--;
            }
            if (i<j) {
                swap(array,i,j);
            }
        }
        swap(array,i,right);
        return i;
    }

快速排序的核心操作是:partition
先找一个基准值,一般是最后一个或者第一个,如果找中间的,需要交换到最前面或者最后面。
找最后一个为基准值的话,先从第一个开始从左往右找比基准值大的元素,再从最后一个开始从右往左找比基准值小的元素,然后交换两个值,重复上述操作,知道前面的指针和后面的指针重合,这个数一定是大于基准值的,和基准值进行交换,然后再对重合位置的左右两边递归进行上述操作,完成整个排序。需要注意的是,当你基准值找的是第一个时,就需要先从右往左找,再从左往右找。
我们可以用一个辅助方法,去完成快速排序,传入他要排序的区间,如果区间只有一个或者没有元素,就不用进行处理直接返回,然后进行partition 操作完成第一次排序,并返回新的基准值的位置,接下来对左右区间进行递归处理。
对于partition 方法,我们找最后一个为基准值,然后用i,j分别指向第一个和最后一个元素,判断i,j是否重合,不重合就进入循环,从前找比基准值大的,从后找比基准值小的,进行交换,出循环后,此时,i,j重合,直接和基准值进行交换,最后返回此时基准值的位置。

时间复杂度:最坏-》O(n^2)
平均-》O(nlogn)
空间复杂度:最坏-》O(n)
平均-》O(logn)
稳定性:不稳定

快速排序非递归:

public static void quickSortByLoop(int[] array) {
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        stack.push(array.length-1);
        while (!stack.empty()) {
            int right = stack.pop();
            int left = stack.pop();
            if (left >= right) {
                continue;
            }
            int index = partition(array,left,right);
            stack.push(index+1);
            stack.push(right);
            stack.push(left);

            stack.push(index-1);
        }
    }

    private static int partition(int[] array, int left, int right) {
        int baseValue = array[right];
        int i = left;
        int j = right;
        while (i < j) {
            while (i<j && array[i] <= baseValue) {
                i++;
            }
            while (i<j && array[j] >= baseValue) {
                j--;
            }
            if (i < j) {
                swap(array,i,j);
            }

        }
        swap(array,i,right);
        return i;
    }

    private static void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

非递归也是参考递归代码进行编写的,依旧需要用到栈,先把需要进行排序的区间进行入栈,如果栈非空进入循环,先后进行两次出栈,如果区间里只有一个元素或者没有,进去下次循环,否则就进行partition操作,再把两边的区间分别入栈。

7.归并排序

 public static void mergeSort(int[] array) {
        mergeSortHelper(array,0,array.length);
    }

    private static void mergeSortHelper(int[] array, int left, int right) {
        if (right-left <= 1) { //前闭后开区间
            return;
        }
        int mid = (left+right)/2;
        mergeSortHelper(array,left,mid);
        mergeSortHelper(array,mid,right);
        merge(array,left,mid,right);
    }

    private static void merge(int[] array, int left, int mid, int right) {
        int cur1 = left;
        int cur2 = mid;
        int[] output = new int[right-left];
        int outputIndex = 0;
        while (cur1 < mid && cur2 < right) {
            if (array[cur1] <= array[cur2]) { //  <=保证稳定性
                output[outputIndex] = array[cur1];
                cur1++;
                outputIndex++;
            }else {
                output[outputIndex] = array[cur2];
                cur2++;
                outputIndex++;
            }
        }
        while (cur1<mid) {
            output[outputIndex] = array[cur1];
            cur1++;
            outputIndex++;

        }
        while (cur2<right) {
            output[outputIndex] = array[cur2];
            cur2++;
            outputIndex++;
        }
        for (int i = 0;i<right-left;i++) {
            array[left+i] = output[i];
        }
    }

归并排序,任然需要一个辅助方法去完成排序,传入要排序的区间的参数,此处的区间是前闭后开的,所以判断是否只有一个元素或者没有元素的时候需要right-left<=1去判断,然后依靠中间下标分为两组,在进行后序遍历递归处理,最后的“访问节点操作”就是排序操作再用一个方法去进行实现。
需要传入前中后三个指针参数,left和mid就是分成的两组的首元素,归并需要额外的空间去进行存储,所以需要一个可以足够存储传进来数组的空间,创建一个right-left大小的数组,用一个变量表示当前数组里有几个元素,当两个数组都不为空时,判断left和mid指向的当前元素的大小,把小的传进新建的数组,然后指针向后移动一位,进行比较,最后出循环判断两个数组的其中一个是否还有元素剩余,把剩余的全部加入新建数组里,最后还需要注意把新建数组里的元素全部按顺序再传回到原数组里。这样整个代码就完成了。

时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章