算法-排序算法總結

插入排序

插入排序思想:每趟將一個元素,按關鍵字大小插入到它前面已經排序的子序列中,依次重複,知直到插入全部元素。
插入排序分爲直接插入排序希爾排序二分插入排序

直接插入排序

在第i趟排序過程中,key = arr[i],arr[0]-arr[i-1]已經有序,從後往前依次將大於key的元素後移,直到遇到小於等於key的元素結束。

代碼實現
/**
 * 直接插入排序,外層循環i代表待插入元素,內層循環j代表待比較元素*/
public static void straightInsert(int[] nums) {
    int len = nums.length;

    for (int i = 1; i < len; i++) {
        int key = nums[i];
        int j = 0;

        for (j = i-1; j >= 0; j--) {
            if (key < nums[j])
                nums[j+1] = nums[j];
            else {
                break;
            }
        }
        nums[j+1] = key;
    }
}
效率分析
時間複雜度O(n^2),空間複雜度O(1)
穩定性分析
由於nums[j]>key是循環進行的判斷條件,關鍵字相等的元素會相遇並進行比較,且排序前後順序保持不變,所以直接插入排序是穩定的

希爾排序

將一個數據序列分成若干組,每組由若干相隔一段距離增量的元素組成,在一個組內使用直接插入排序;增量通常設置爲數據序列長度的一半,以後每次減半,直到爲1。

代碼實現
/**
 * 希爾排序,外層循環delt控制步長,中層循環i代表待插入元素,內層循環j代表待比較元素*/
public static void shellSort(int[] nums) {
    int len = nums.length;

    for (int delt = len / 2; delt > 0; delt /= 2) {
        for (int i = delt; i < len; i++) {
            int key = nums[i];
            int j = 0;

            for (j = i - delt; j >= 0; j -= delt) {
                if (key < nums[j])
                    nums[j+delt] = nums[j];
                else {
                    break;
                }
            }

            nums[j+delt] = key;
        }
    }
}
複雜度分析
時間複雜度O(n*(log(n))^2)),空間複雜度O(1)
穩定性分析
由於增量的存在,希爾排序過程中會錯過相鄰元素的比較,排序算法不穩定。

交換排序

交換排序分爲冒泡排序快速排序

冒泡排序

代碼實現
/**
 * 冒泡排序(升序),外層循環控制排序趟數,內層循環控制當前待比較元素*/
public static void bubbleSort(int[] nums) {
    int len = nums.length;

    for (int i = 1; i < len; i++) {
        for (int j = 0; j < len - i; j++) {
            if (nums[j] > nums[j+1]) {
                int tmp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = tmp;
            } else
                continue;
        }
    }
}
複雜度分析
時間複雜度O(n^2),空間複雜度O(1)
穩定性分析
冒泡排序由於相鄰元素一定會進行比較,且nums[j] > nums[j+1]作爲判斷條件,所以是穩定的

快速排序

代碼實現
    /**
     * 快速排序算法*/
public static void quickSort(int[] nums) {
    quickSort(nums, 0, nums.length - 1);
}
private static void quickSort(int[] nums, int start, int end) {
    if (0 <= start && start < end && end < nums.length) {
        int front = start;  // 初始頭索引
        int tail = end;     // 初始尾索引
        int key = nums[front];  // 選擇當前序列的第一個元素爲參考元素

        while (front < tail) {

            //從後向前,找到第一個小於參考元素的索引
            while (nums[tail] >= key && front < tail) {
                tail--;
            }

            //從後面找到了滿足條件的元素
            if (front < tail) {
                nums[front++] = nums[tail]; // 移動後面的元素到前面,並更新頭指針
            }

            //從前向後,找到第一個大於參考元素的索引
            while (nums[front] <= key && front < tail) {
                front++;
            }

            //找到了滿足條件的元素
            if (front < tail) {
                nums[tail--] = nums[front]; // 移動前面的元素到後面,並更新尾指針
            }

        }

        nums[front] = key;

        quickSort(nums, start, front - 1);
        quickSort(nums, tail + 1, end);
    } else
        return;
}
複雜度分析
時間複雜度O(n*log(n)),空間複雜度O(log(n))
穩定性分析
快速排序算法是不穩定的

選擇排序

選擇排序包含直接選擇排序堆排序

直接選擇排序

基本思想
第一趟從n個元素的數據中選擇最小值,放在最前位置;下一趟從n-1個元素中選出最小值,放在次前位置。以此類推,經過n-1趟完成排序。
代碼實現
/**
 * 直接選擇排序*/
public static void straightSelect(int[] nums) {
    int len = nums.length;

    //外層循環趟數,每一趟確定一個最小值;內層循環代表待比較元素
    for (int i = 0; i < len - 1; i++) {
        int min = i;    // 存放最小值索引

        for (int j = i + 1; j < len; j++) {
            if (nums[j] < nums[min])
                min = j;
        }

        //交換
        int tmp = nums[i];
        nums[i] = nums[min];
        nums[min] = tmp;
    }
}
複雜度分析
時間複雜度O(n^2),空間複雜度O(1)
穩定性分析
直接選擇排序會對不相鄰的元素進行交換,所以是不穩定的

堆排序

代碼實現
/**
 * 堆排序*/
public static void heapSort(int[] nums) {
    int len = nums.length;

    //創建最小堆
    for (int parent = len / 2; parent >= 0; parent--) {
        sift(nums, parent, len - 1);
    }

    //將根節點和最後一個節點交換位置,nums長度-1,然後再次調整堆
    for (int i = len - 1; i > 0; i--) {
        //將根節點與最後一個子節點交換
        {
            int tmp = nums[0];
            nums[0] = nums[i];
            nums[i] = tmp;
        }

        //重新調整堆
        sift(nums, 0, i - 1);
    }
}

private static void sift(int[] nums, int parent, int end) {
    int child = parent * 2 + 1; //左孩子
    int key = nums[parent];     //當前父節點元素

    while (child <= end) {  //當前節點存在子節點
        //判斷右孩子是否存在,若存在,child指向較小的孩子
        if (child + 1 < end && nums[child + 1] < nums[child]) {
            child++;
        }

        //判斷父節點和當前孩子節點的大小關係
        if (key > nums[child]) {
            //孩子節點上移
            nums[parent] = nums[child];
            //父節點指針下移
            parent = child;
            //更新子節點
            child = parent * 2 + 1;
        } else {
            break;
        }
    }

    nums[parent] = key;
}
複雜度分析
時間複雜度O(n*log(n)),空間複雜度O(1)
穩定性分析
堆排序不穩定

歸併排序

歸併排序分爲自上而下自下而上兩種

自上而下

自上而下歸併是一種遞歸的方法,分爲兩種操作:拆分操作、合併操作

代碼實現
/**
 * 歸併排序算法:自頂向下遞歸排序*/
public class MergeSort2 {
    //合併操作,根據索引合併兩個子序列
    public static void merge2(int[] x, int start, int mid, int end) {
        int[] tmp = new int[end - start + 1];
        int index1 = start, index2 = mid + 1, index = 0;

        while (index1 <= mid && index2 <= end) {
            if (x[index1] < x[index2])
                tmp[index++] = x[index1++];
            else
                tmp[index++] = x[index2++];
        }

        while (index1 <= mid)
            tmp[index++] = x[index1++];
        while (index2 <= end)
            tmp[index++] = x[index2++];

        //移動數組元素
        for (int i = 0; i < tmp.length; i++)
            x[start + i] = tmp[i];
    }

    //拆分排序
    public static void sort2(int[] x, int start, int end) {
        if (start < end) {  //遞歸進行的條件
            int mid = (start + end) / 2;

            sort2(x, start, mid);
            sort2(x, mid + 1, end);
            merge2(x, start, mid, end);
        }
    }

    public static void mergeSort2(int[] x) {
        sort2(x, 0, x.length - 1);
    }
}

自下而上歸併

代碼實現
/**
 * 歸併排序算法:自底向上歸併*/
public class MergeSort {
    //遍歷當前數組
    public static void printNums(int[] nums) {
        int len = nums.length;
        StringBuffer sb = new StringBuffer("(");

        int index = 0;
        for (index = 0; index < len - 1; index++)
            sb.append(nums[index] + ",");
        sb.append(nums[len - 1]);
        sb.append(")\n");

        System.out.println(sb.toString());
    }

    //一次歸併,合併相鄰的子序列
    public static void merge(int[] x, int[] tmp, int begin1, int begin2, int n) {
        int len = x.length;

        int index1 = begin1, index2 = begin2, index = index1;   //索引

        while (index1 < begin1 + n && index2 < begin2 + n && index2 < len) {
            if (x[index1] < x[index2])
                tmp[index++] = x[index1++];
            else
                tmp[index++] = x[index2++];
        }

        while (index1 < begin1 + n && index1 < len)
            tmp[index++] = x[index1++];
        while (index2 < begin2 + n && index2 < len)
            tmp[index++] = x[index2++];
    }

    //一趟歸併,合併兩兩相鄰的子序列
    public static void mergePass(int[] x, int n) {
        int len = x.length;
        int[] tmp = new int[len];       //輔助數組

        for (int begin = 0; begin < len; begin += 2*n) {
            merge(x, tmp, begin, begin + n, n);
        }

        //移動數組元素
        for (int index = 0; index < len; index++)
            x[index] = tmp[index];
    }

    //歸併排序
    public static void mergeSort(int[] x) {
        int len = x.length;
        for (int n = 1; n < len; n *= 2) {
            mergePass(x, n);
        }
    }
}
複雜度分析
時間複雜度爲O(n*log(n)),空間複雜度爲O(n)
穩定性分析
歸併排序相鄰元素會進行比較,排序是穩定的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章