7가지 정렬 알고리즘 및 카운팅 정렬


다음 정렬은 작은 정렬부터 큰 정렬까지를 예로 들어 설명합니다.

1. 직접 삽입 정렬

시간 복잡도:
최선의 경우: 완전히 정렬됨 1 2 3 4 5 O(N)
최악의 경우: 완전히 역전됨 5 4 3 2 1 O(N^2) (산술 시퀀스 합산과 같음) 공간 복잡도:
O(1) 안정성
:
안정적 주어진 데이터가 더 정렬된 경우 직접 삽입 정렬이 더 빠름
기본적으로 정렬된 데이터 집합이 있는 경우 직접 삽입 정렬을 사용하는 것이 좋습니다.

public static void insertSort(int[] array) {
    
    
        for(int i = 1; i < array.length; i++) {
    
    
            int j = i - 1;
            int tmp = array[i];
            for(; j >= 0; j--) {
    
    //跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {
    
    //如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
    
    
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

2. 힐 정렬

Hill Sorting은 Direct Insertion Sorting의 최적화로, Jumping grouping은 가능한 한 작은 요소를 앞으로 이동시킬 수 있음
몇 개의 증분(갭이 얼마나 되는가)을 그룹으로 나누는가
시간복잡도: N^1.3 ~ N^1.5
공간복잡도: O(1)
안정성: 불안정

public static void shellSort(int[] array) {
    
    
        int gap = array.length;//增量为多少(gap为多少),则被分为多少组
        while(gap > 1) {
    
    //里面已经包括了排序gap为1的情况
            gap /= 2;
            shell(array, gap);
        }
    }
    private static void shell(int[] array, int gap) {
    
    
        for(int i = gap; i < array.length; i++) {
    
    //i+=gap也行,因为gap最后会变为1
            int j = i - gap;
            int tmp = array[i];
            for(; j >= 0; j -= gap) {
    
    //跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {
    
    //如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + gap] = array[j];
                }else {
    
    
                    break;
                }
            }
            array[j + gap] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

3. 직접 선택 정렬

시간 복잡도: 상황이 좋든 나쁘든 다음 두 가지 쓰기 방법은 O(N^2)(연산 시퀀스의 합과 같음) 공간
복잡도: O(1)
안정성: 불안정

public static void selectSort1(int[] array){
    
    
        for(int i = 0; i < array.length; i++) {
    
    
            int minIndex = i;
            for(int j = i + 1; j < array.length; j++) {
    
    
                if(array[j] < array[minIndex]) {
    
    
                    minIndex = j;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                }
            }
            swap(array, i, minIndex);
        }
    }
    private static void swap(int[]array, int i, int j) {
    
    
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    //写法二
    public static void selectSort2(int[] array){
    
    
        int left = 0;
        int right = array.length - 1;
        while(left < right) {
    
    //与写法一相比将写法一最外层的for循环改为了while循环
                int minIndex = left;
                int maxIndex = left;//maxIndex一定是left,因为下面的j下标是从left+1开始从前往后遍历
                for(int i = left + 1; i <= right; i++) {
    
    
                    if(array[i] < array[minIndex]) {
    
    
                        minIndex = i;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                    }
                    if(array[i] > array[maxIndex]) {
    
    
                        maxIndex = i;//在所有的j下标元素中找比array[maxIndex]还要大的元素的下标
                    }
                }
                swap(array, left, minIndex);
                //当最大值原来刚好在最小值的位置(left位置)时,则上一步已经将最小值与left交换,此时最大值在原来最小值的位置,
                if(maxIndex == left) {
    
    //所以要做这一步操作
                    maxIndex = minIndex;
                }
                swap(array, right, maxIndex);
                left--;
                right++;
        }
    }

4. 힙 정렬

시간복잡도: O(N)(대형 루트 힙 생성) + O(N logN)(힙의 각 노드 하향 조정) 또는 O(N logN)
공간복잡도: O(1)
안정성: 불안정
데이터 양이 매우 많을 때 Hill 정렬의 시간복잡도는 N^1.3 ~ N^1.4이고, Heap 정렬은 대수, Hill 정렬은 지수함수이기 때문에 Heap 정렬이 Hill 정렬보다 빨라야 함

public static void heapSort(int[] array){
    
    
        creatBigHeap(array);
        int end = array.length - 1;
        while(end > 0) {
    
    
            swap(array, 0, end);//因为是大堆,堆顶的元素最大,将堆顶元素与队尾元素交换,使队尾元素变成队内最大元素
            shiftDown(array, 0, end);//在这里end=array.length-1,而在shiftDown中end代表数组的总长度,
            end--;                          //相当于去掉队尾元素再进行向下调整
        }
    }
    private static void creatBigHeap(int[] array) {
    
    
        for(int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
    
    
            shiftDown(array, parent, array.length);
        }
    }
    private static void shiftDown(int[] array, int parent, int end) {
    
    
        int child = 2 * parent + 1;
        while(child < end) {
    
    //因为end传的是array.length,所以条件用<而不用<=
            if(child + 1 < end && array[child] < array[child + 1]) {
    
    
                child++;
            }
            if(array[child] > array[parent]) {
    
    
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;
            }else {
    
    
                break;
            }
        }
    }

다섯, 버블 정렬

시간 복잡도: O(N^2) 최적화 후 최상의 경우(한 번만 비교)는 O(N)
공간 복잡도: O(1)
안정성: 안정적

public static void bubbleSort(int[] array){
    
    
        for (int i = 0; i < array.length - 1; i++) {
    
    
            boolean flg = false;
            for (int j = 0; j < array.length - 1 - i; j++) {
    
    
                if(array[j] > array[j + 1]) {
    
    
                    swap(array, j, j + 1);
                    flg = true;
                }
            }
            if(!flg) {
    
    
                return;
            }
        }
    }

여섯, 빠른 정렬

퀵 정렬(벤치마크를 루트로 하여 이진 트리를 생성하는 것과 같음) 시간
복잡도:
최선의 경우: O(N*logN) 완전 이진 트리/완전 이진 트리
최악의 경우: O(N^2)(산술 시퀀스의 합과 같음) 단일 분기 트리 공간 복잡도: 최선의 경우: O
(
logN) 완전 이진 트리/완전 이진 트리의 높이 최악의 경우: O
(N) 단일 분기 트리가 N개의 노드를 생성 안정성
: 불안정
세 가지 벤치마킹 방법: Hoare 방식 , 파기 방식, 정방향 및 역방향 포인터 방식

6.1 재귀적 퀵 정렬

여기에서 재귀적으로 구현된 퀵정렬은 두 가지 최적화가 있는데, 첫 번째 최적화는 3개 숫자의 중간을 취하여 원래 벤치마크를 찾는 방법을 사용하여 생성된 트리가 더 완전한 이진 트리에 가까워 트리의 높이를 줄이고 공간 복잡도를 줄이는 것입니다. 재귀
여기에 이미지 설명 삽입
횟수

public static void quickSort(int[] array){
    
    
        quickSortF(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void quickSortF(int[] array, int left, int right) {
    
    
        if(left >= right) return;//这是递归的结束条件,有=,因为left=right时不用再创建节点了,这个节点已经有序
        //递归到后面的较小区间时用插入法
        if(right - left + 1 <= 7) {
    
    //随着快速排序的进行,整个数据正在趋于有序,当区间越来越小且区间的内容越来越有序时,
            insertSort1(array, left, right);//这部分区间可以用插入排序,这样做可以减少递归的次数
            return;
        }
        //三数取中法,降低了二叉树的高度,降低了空间复杂度,使空间复杂度变为O(logn)
        int midIndex = midOfTree(array, left, right);
        swap(array, midIndex, left);//交换完之后保证left下标是三个数中中间大的数字
        int pivot = partition2(array, left, right);//找到一次基准相当于排好了当前基准这个元素在数组中的顺序,找到基准后,基准左边都是比基准小的,基准右边都是比基准大的
        //先递归左边,递归完左边再递归右边
        quickSortF(array, left, pivot - 1);
        quickSortF(array, pivot + 1, right);
    }
    //插入法
    private static void insertSort1(int[] array, int left, int right) {
    
    
        for(int i = left + 1; i <= right; i++) {
    
    
            int j = i - 1;
            int tmp = array[i];
            for(; j >= left; j--) {
    
    //跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {
    
    //如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
    
    
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }
    //在三数中找到中间大小数的下标
    private static int midOfTree(int[] array, int left, int right) {
    
    
        int mid = (left + right) / 2;
        if(array[left] < array[right]) {
    
    
            if(array[mid] < array[left]) {
    
    
                return left;
            }else if(array[mid] > array[right]) {
    
    
                return right;
            }else {
    
    
                return mid;
            }
        }else {
    
    
            if(array[mid] > array[left]) {
    
    
                return left;
            }else if(array[mid] < array[right]) {
    
    
                return right;
            }else {
    
    
                return mid;
            }
        }
    }
    //三种找基准的方法,在排序的过程中序列可能会不一样,但最终排好序的结果一样,建议优先使用挖坑法,right参数传的都是array.length-1
    //Hoare法找基准
    private static int partition1(int[] array, int left, int right) {
    
    
         int key = array[left];
         int i = left;//将基准的下标保存到i中,因为后面要用left与right相交位置的元素与基准进行交换,这里的交换函数只传下标
        while(left < right) {
    
    
            while (left < right && array[right] >= key) {
    
    //left<right这个条件很重要,因为right不断--,此时再判断array[right]>=key时array[right]有可能会越界
                right--;//先走right,因为一开始基准为left,先走right,left和right相遇的地方才能是比基准小的,才能把比基准小的与基准交换放在基准前
            }
            while (left < right && array[left] <= key) {
    
    //array[left]<=key或上面array[right]>=key的=一定要有,否则可能会进入死循环(如当left和right的值相同时)
                left++;
            }
            swap(array, left, right);
        }
        swap(array, left, i);
        return left;
    }
    //挖坑法找基准
    private static int partition2(int[] array, int left, int right) {
    
    
        int key = array[left];
        while(left < right) {
    
    
            while (left < right && array[right] >= key) {
    
    
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= key) {
    
    
                left++;
            }
            array[right] = array[left];
        }
        array[left] = key;
        return left;
    }
    //前后指针法找基准
    private static int partition3(int[] array, int left, int right) {
    
    
       int prev = left;
       int cur = left + 1;
       while(cur <= right) {
    
    
           if(array[cur] < array[left] && array[++prev] != array[cur]) {
    
    //cur在前面找比基准小的,prev保持在cur找到比基准小的之后要跟prev交换的那个位置,prev是前置++
               swap(array, prev, cur);//代码走到这说明cur和prev拉开了距离且cur<left,prev>=left
           }
           cur++;//array[cur]>=array[left]时cur直接往前走,与prev拉开距离
       }
       swap(array, prev, left);
       return prev;
    }

6.2 비재귀적 퀵 정렬

public static void quickSortNor(int[] array) {
    
    
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        int piovt = partition2(array, left, right);
        if(piovt - 1 > left) {
    
    
            stack.push(left);//往栈上先放left后放right,则出栈时先出right后出left
            stack.push(piovt - 1);
        }
        if(piovt + 1 < right) {
    
    
            stack.push(piovt + 1);
            stack.push(right);
        }
        while(!stack.isEmpty()) {
    
    //因为这里出栈时先出right后出left,根据出出来的right和left找新的基准,所以相当于先递归二叉树右边,再递归二叉树左边
            right = stack.pop();
            left = stack.pop();
            piovt = partition2(array, left, right);
            if(piovt - 1 > left) {
    
    
                stack.push(left);
                stack.push(piovt - 1);
            }
            if(piovt + 1 < right) {
    
    
                stack.push(piovt + 1);
                stack.push(right);
            }
        }
    }

일곱, 병합 정렬

시간 복잡도: O(N*logN) 머지 정렬은 머지 과정에서 각 레이어를 N번 순회해야 함, 총 logN 레이어
공간 복잡도: O(N) 병합 정렬은 마지막에 머지할 때 원래 배열과 정확히 같은 크기의 추가 배열을 적용해야 함 병합 정렬의 단점은 공간 복잡도가 너무 크다 안정성 :
안정적
.

여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

7.1 병합 정렬의 재귀적 구현

public static void mergeSort(int[] array) {
    
    
        mergeSort(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void mergeSort(int[] array, int left, int right) {
    
    
        if(left >= right) return;
        int mid = (left + right) / 2;
        //分裂左边
        mergeSort(array, left, mid);
        //分裂右边
        mergeSort(array, mid + 1, right);
        //合并,在合并的过程中进行了排序
        merge(array, left, right, mid);
    }
    private static void merge(int[] array, int left, int right, int mid) {
    
    
        int s1 = left;
        int s2 = mid + 1;
        int[] tmpArr = new int[right - left + 1];//申请一个新的数组,大小为right-left+1
        int k = 0;
        while(s1 <= mid && s2 <= right) {
    
    //满足这个循环条件说明s1~mid和s2~right这两个区间都同时有数据
            if(array[s2] < array[s1]) {
    
    //若条件改为array[s2]<=array[s1]则排序变为不稳定
                tmpArr[k++] = array[s2++];
            }else {
    
    
                tmpArr[k++] = array[s1++];
            }
        }
        while(s1 <= mid) {
    
    
            tmpArr[k++] = array[s1++];
        }
        while(s2 <= right) {
    
    
            tmpArr[k++] = array[s2++];
        }
        for (int i = 0; i < tmpArr.length; i++) {
    
    
            array[i + left] = tmpArr[i];//将tmpArr数组拷回原来数组时,赋给array[i+left],因为原来数组的left有可能不是0
        }
    }

7.2 병합 정렬의 비재귀적 구현

병합하기 전에 그룹의 수는 계속 감소하고 각 요소 그룹은 계속 증가합니다. 하나씩 그룹으로 (간격이 1) 다음 두 개와 두 그룹으로 (간격이 2) 병합하기 전에 중간 및 오른쪽이 경계를 넘을 수 있으며 이때 조정이 필요합니다
여기에 이미지 설명 삽입
.
여기에 이미지 설명 삽입

public static void mergeSortNor(int[] array) {
    
    
        int gap = 1;
        while(gap < array.length) {
    
    
            for (int i = 0; i < array.length; i += 2*gap) {
    
    
                int left = i;
                int mid = left + gap - 1;
                int right = mid + gap;
                if(mid >= array.length) {
    
    //mid有可能会越界,越界时要做出调整
                    mid = array.length - 1;
                }
                if(right >= array.length) {
    
    //right有可能会越界,越界时要做出调整
                    right = array.length - 1;
                }
                merge(array, left, right, mid);
            }
            gap *= 2;
        }
    }

8. 계산 및 분류

시간 복잡도: O(2N+데이터 범위) 또는 O(MAX(N, 데이터 범위))
공간 복잡도: O(데이터 범위)
안정성: 안정적이나 다음 코드로 구현된 카운팅 정렬이 불안정함
카운팅 정렬은 특정 간격으로 집중된 데이터를 정렬하는 데 적합함

   public static void countSort(int[] array) {
    
    
        int minVal = array[0];
        int maxVal = array[0];
        //求数组中的最大值和最小值
        for (int i = 0; i < array.length; i++) {
    
    
            if(array[i] < minVal) {
    
    
                minVal = array[i];
            }
            if(array[i] > maxVal) {
    
    
                maxVal = array[i];
            }
        }
        int[] count = new int[maxVal - minVal + 1];//依据最大值和最小值来确定计数数组的大小
        //遍历原来的数组进行计数
        for (int i = 0; i < array.length; i++) {
    
    
            count[array[i] - minVal]++;//计数数组的下标相当于要排序数组的元素内容
        }
        //遍历count,把当前元素写回array
        int index = 0;
        for (int i = 0; i < count.length; i++) {
    
    
            while(count[i] != 0) {
    
    
                array[index] = i + minVal;
                index++;
                count[i]--;
            }
        }
    }

Supongo que te gusta

Origin blog.csdn.net/zhanlongsiqu/article/details/131876104
Recomendado
Clasificación