데이터 구조 및 알고리즘 분석----8가지 정렬(힙 정렬이 일시적으로 부족함)

개요

정렬은 내부 정렬과 외부 정렬로 나뉩니다.

여기에 이미지 설명 삽입
Baidu에서 침입 및 삭제

8개의 주요 정렬은 모두 내부 정렬
시간 복잡도에 속합니다.
여기에 이미지 설명 삽입
Baidu에서 침입 및 삭제

안정성은 길이는 같지만 내용이 다른 두 개의 시퀀스를 정렬할 때 속도가 기본적으로 같은지 여부를 말하며 기본적으로 동일하면 안정적이고 그렇지 않으면 불안정합니다.

이런 종류의 경우 기본적으로 데이터 교환 횟수에 따라 속도가 달라지는 것 같은데, 일반적으로 교환 횟수가 적을수록 속도가 빨라집니다. 값을 비교하거나 할당하기만 하는 경우에는 일반적으로 조금 더 많이 사용해도 효과가 거의 없습니다. 너무 많은 빠른 정렬은 시간을 위해 공간을 사용합니다. 이것이 다음 정렬 중 일부는 빠르고 일부는 느린 이유입니다.

정렬이 시작되기 전에

다음 정렬 예에서는 작은 것에서 큰 것으로 정렬합니다.
먼저 아래의 모든 정렬에 필요한 속도 측정 방법에 대해 이야기해 보겠습니다.
여기에 이미지 설명 삽입
for 루프는 많은 난수를 생성하는 데 사용되며 아래 빨간색 선은 타이밍입니다.

앞선 아이디어에 대한 분석과 설명을 바탕으로 코드를 살펴보자.

버블링 + 선택

둘 중 가장 간단하고 대중적인,
첫 번째는 버블링

거품

버블은 8가지 종류 중 가장 단순하다고 할 수 있으며, 그 원리는 인접한 두 숫자를 서로 비교한 다음 가장 큰 값을 얻은 다음 나머지 숫자를 비교하여 가장 큰 값을 얻는 것입니다. 최대값.얻은 최대값의 개수가 모든 숫자의 개수보다 1이 적으면 정렬이 완료되었다는 뜻입니다.자세한 아이디어:
먼저
시퀀스, 이 시퀀스를 처음부터 끝까지 놓고, 순차적으로 비교합니다. 두 개의 숫자, 두 개의 숫자를 비교할 때마다 이전 숫자가 더 크면 두 숫자의 위치가 바뀌므로 비교가 시퀀스의 끝에 도달하면 시퀀스에서 최대 값을 얻을 수 있습니다. 시퀀스의 끝에 배치한 후 가장 많은 값을 이동하지 않고 시퀀스의 나머지 데이터를 처음부터 끝까지 비교한 다음 정렬이 끝날 때까지 위의 과정을 반복합니다.
정렬 종료 신호:
정렬되지 않은 시퀀스에 값이 하나만 남아 있거나 처음부터 끝까지 비교하는 경우 값 교환이 발생하지 않습니다.

암호

아래는 버블링 코드입니다.
여기에 이미지 설명 삽입이것은 비교적 간단하므로 더 이상의 설명은 생략합니다.

80000 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
이 효율성은 여전히 ​​상대적으로 낮습니다. 80000은 10초가 걸렸습니다.

모든 코드

다음과 같이:

public class BubbleSort {
    
    

    public static void main(String[] args) {
    
    
        BubbleDemo bubble=new BubbleDemo();

        int[] array=new int[80000];
        for (int i = 0; i < 80000; i++) {
    
    
            array[i]= (int) (Math.random()*80000);  //Math.random可以获得一个0到1之间的随机浮点数
        }
        Long time01=System.currentTimeMillis();
//        该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。

        bubble.Bubble(array);
        Long time02=System.currentTimeMillis();
        System.out.println("排序80000个数据共消耗了(毫秒)"+(time02-time01)+"毫秒");
    }
}
class BubbleDemo{
    
    
    public int[] Bubble(int[] array){
    
    
        int temp=0;
        boolean flag=false;
        for (int i = 0; i < array.length - 1; i++) {
    
    
            for (int j = 0; j < array.length - 1 - i; j++) {
    
    
                if (array[j]>array[j+1]){
    
    
                    flag=true;
                    temp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=temp;
                }
            }
            if (!flag){
    
    
                break;
            }else {
    
    
                flag=false;
            }
        }

        return array;
    }
}

선택 정렬

선택정렬은 버블정렬보다 조금 까다롭습니다
선택정렬의 원리 :
원리 : 약간 버블링과도 비슷합니다 매번 무질서한 시퀀스에서 가장 많은 값을 찾은 다음 가장 큰 값을 맨 앞에 두는 것입니다 , 난수열에서 최대값을 제거한 후 남은 난수열을 찾아 제거
데이터의 위치는
다음과 같음
선택 정렬 : 시간복잡도 : O(n^2)
원칙 : 매번 시퀀스에서 가장 큰 값을 찾아 시퀀스의 맨 앞에 놓는다.
일반 원칙 : 매번 가장 큰 값을 찾아 시퀀스의 맨 앞 뒤에 이 값의 위치를 ​​고정하고, 다음 시퀀스에서 가장 높은 값은 이 고정된 값 뒤에 배치됩니다. 시퀀스에서 고정되지 않은 값이 하나만 남아 있으면 정렬이 끝난 것입니다. 추가 원칙: 시퀀스가 ​​먼저 발견되었다고 가정합니다.
최소값 이 가 가장 작은 값이면 시퀀스의 맨 앞에 있는 데이터와 최소값을 교환합니다. 이때 첫 번째 정렬이 완료되고 다음 정렬에 들어간다는 의미입니다. 시퀀스의 선두 위치를 제거(즉, 마지막으로 찾은 최소값)하고, 나머지 시리즈에서 최소값을 찾아 찾은 후 위의 작업을 반복한다.

암호

성취하다:
여기에 이미지 설명 삽입

80000 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
버블링보다 훨씬 빠름

위의 작은 지식 포인트도 있습니다

즉, JVM이 유지하는 나노초는 위의 참고 사항을 참조하십시오.

모든 코드

다음과 같이:

import java.util.Arrays;

public class SelectSort {
    
    
    public static void main(String[] args) {
    
    
        SelectSortDemo SortDemo=new SelectSortDemo();
        int array[]=new int[80000];
        for (int i = 0; i < 80000; i++) {
    
    
            array[i]=(int)Math.random()*80000;
        }
        Long time1=System.currentTimeMillis();
        SortDemo.Sort(array);
        Long time2=System.currentTimeMillis();
        System.out.println("排序80000个数据耗时(毫秒)"+(time2-time1));

//        System.nanoTime()返回的是纳秒,nanoTime而返回的可能是任意时间,甚至可能是负数。
//        每个JVM维护一份,和系统时间无关,可用于计算时间间隔,比System.currentTimeMillis的精度要高。
//        修改了系统时间会对System.currentTimeMillis造成影响,而对System.nanoTime没有影响
//        该函数只能用于计算时间差,不能用于计算距离现在的时间。
    }
}
//        选择排序:时间复杂度:O(n^2)
//        原理:每次找到数列中的最值,并将其放在数列头部
//        大致原理:每次找到最值并将其放在数列头部后,就固定此值的位置,后面数列中的最值放在这些固定值的后面,当数列中仅剩一个值未固定,则表示排序结束
//        进一步的原理:假设首先找到了数列中最小的值,则将此最小值和数列头的数据进行交换,此时则表示完成第一次排序,
//        然后进入下一次排序,在下一次找最小值的数列中,去除掉数列头的位置(即上次寻找到的最小值),
//        在剩余的数列中寻找最小值,找到后重复上面的操作
class SelectSortDemo{
    
    
    public int[] Sort(int[] array){
    
    
//        首先肯定是需要两层for循环嵌套。外层的for循环用于控制次数,即需要寻找几次最值。
//        当所有的数列中的值只剩下一个没被固定,则表示排序完成,所以此处是0~array.length-1。即数列中数值的数量-1
        for (int i = 0; i <array.length-1 ; i++) {
    
    

//            每次的寻找最小值都需要先拥有一个值,我们此处得到第一个值当做初始的值,
//            简单来说,就是在还未找到最小值的那部分数列中,首先取其第一个值,用于二层循环中的第一次比较
            int n=array[i];   //得到第一个值
            int m=i;    //得到第一个值对应的索引,用于后续的交换

//            二层for循环,这个for循环用于控制比较和交换,j同时用于作为数组的索引
//            首先,第一个值我们已经有了,索引我们要跳过第一个值,所以j从i+1开始,
//            这里是需要比较的,即我们需要验证数列中的所有数值,所以此处索引应该到达array.length-1,所以此处是< array.length
            for (int j = 1+i; j < array.length; j++) {
    
    

//                进行比较,如果第一个值不是最值
                if (n>array[j]){
    
    
//                    我们则抛弃第一个值,来使用此时找到的最值。(后续的for循环中会持续找最值并更新最值,知道for结束)
                    n=array[j];
//                    并更新对应的索引
                    m=j;
                }
            }

//            上面而层for循环结束,则表示找到了一个最值,若此最值不在数列头部,则将其和数列头部数据进行交换,将其放在当前所属数列的头部,
            if (m!=i) {
    
    
                array[m] = array[i];
                array[i] = n;
            }
        }
        return array;
    }
}

병합 정렬

병합 정렬은 분할 정복 알고리즘을 사용합니다.
분할 정복 알고리즘의 경우 Baidu Encyclopedia에서는 다음과 같이 말합니다. 분할 정복 알고리즘의 기본 아이디어는 크기 N의 문제를 K보다 작게 분해하는 것입니다. 서로 독립적이고 원래 문제와 관련된 하위 문제 문제는 동일한 성격을 가집니다. 하위 문제에 대한 솔루션을 찾으면 원래 문제에 대한 솔루션을 얻을 수 있습니다. 즉, 부목적 완성 프로그램 알고리즘, 간단한 문제는 이분법으로 완성할 수 있다.
여기서 사용:
재귀로 시리즈를 나누고, 매번 현재 시리즈의 중간 값을 취하여 시리즈를 중간 값에서 왼쪽과 오른쪽 부분으로 나누고 재귀를 사용하여 왼쪽과 오른쪽 시리즈만 남을 때까지 나눕니다. 시리즈에 하나의 값이 남아 있습니다. 분할을 중지하십시오. 위의 프로세스는 분할 프로세스 이고
거버넌스 프로세스 는 재귀 역 추적 프로세스입니다. 두 시퀀스를 정렬 및 통합하고 인접한 두 시퀀스가 ​​처리 될 때마다 병합 한 다음 결합합니다. 두 개의 시퀀스로 구성된 다른 시퀀스와 함께 병합된 어레이가 통합되고 처리됩니다.
다음은 더 자세한 아이디어입니다.

병합 정렬
일반적으로 병합 정렬이 더 간단한 것 같습니다.

  1. 아이디어 : 분할 정복 알고리즘을 사용하여 일련의 숫자를 분할 정복합니다. 일련의 숫자를 먼저 분리한 다음 분리된 일련의 숫자를 차례로 처리하는 것입니다.
  2. 일반적인 아이디어 : 재귀를 사용하여 시퀀스를 나누고, 시퀀스를 매번 두 부분으로 나누고, 각 시퀀스에 하나의 값만 남을 때까지 재귀하고, 이때 역추적을 시작합니다. 백트래킹 과정에서 2개의 시퀀스를 정렬하여 통합하고 인접한 2개의 시퀀스를 처리할 때마다 병합한 후 두 시퀀스를 병합하여 형성된 다른 시퀀스와 통합한다.
  3. 추가 아이디어 및 원칙: (다음은 작은 것부터 큰 것까지의 예입니다.)
    우선, 우리는 두 개의 인접한 시리즈를 처리하는 방법을 알아야 합니다. 여기에서 이를 수행하고 메서드를 작성하고 다음을 포함한 여러 매개 변수를 받습니다. 오른쪽, 중간 및 원래 배열과 임시 배열에 대한 대부분의 인덱스. 이 인덱스는 원래 배열에서 처리할 시리즈를 가져오고 중간 값에 따라 두 시리즈를 왼쪽과 오른쪽 부분으로 나누는 데 사용됩니다. 임시 배열은 두 배열을 병합하고 정렬하는 데 사용되며 동시에 병합된 배열을 원래 배열의 위치에 놓습니다.
    병합 및 처리 프로세스:
    먼저 인덱스에 따라 왼쪽 및 오른쪽 배열을 가져옵니다. 가운데 값으로 왼쪽과 오른쪽 배열을 각각 A, B, A로 설정하고 가장 왼쪽에서 가운데 ​​값으로 B를 가운데 값 + 1에서 가장 오른쪽으로 매번 하나의 시퀀스에서 하나의 데이터를 꺼내고 다른 시퀀스의 데이터와 차례로 비교합니다.
    먼저 가장 왼쪽 및 가장 오른쪽 인덱스를 기록합니다(나중에 데이터를 임시 배열에 다시 배열로 넣기 위해).
    매번 A에서 하나를 가져와 B의 데이터와 차례로 비교한다고 가정하고 작은 데이터를 임시 배열에 넣고 동시에 인덱스를 변경하고 A의 값이 작다고 가정하고 임시 배열의 A에 있는 데이터를 A의 현재 인덱스를 한 비트 뒤로 이동하고 동시에 임시 배열의 현재 인덱스를 한 비트 뒤로 이동(다음 데이터 저장을 위해)한 다음 가리키는 데이터를 비교합니다
    . A의 현재 인덱스와 숫자 열 B로 위의 작업을 반복하여 특정 시퀀스가 ​​비어 있을 때까지 다른 시퀀스의 나머지 데이터를 임시 배열에 순서대로 로드한 다음 정렬된 시퀀스를 가져온 다음 시퀀스를 교체합니다
    . 원래 시퀀스에 있는 것과 임시 배열에 넣습니다. 이때 여기에는 정렬된 시퀀스가 ​​있으며 나중에 다른 시퀀스와 비교할 것이며 전체 데이터 정렬이 완료될 때까지 위의 작업을 반복합니다
    .

참고 : 두 개의 숫자열에 대한 병합 프로세스는 하나의 값만 있는 두 개의 숫자열에서 시작하기 때문에 매번 정렬 순서가 순서대로 정렬되므로 사실 두 숫자열의 융합이 정렬될 때마다 두 개의 정렬된 시퀀스의 공동 정렬입니다.

전체 배열에 대해 위에서 언급한 작업을 매우 영리하게 수행하려면 배열을 그룹화하고 처리하는 매우 영리한 재귀가 필요합니다.

  1. 그룹화 및 재귀적 역추적:
    메소드를 다시 작성하고 요소 그룹, 임시 배열, 맨 왼쪽 인덱스, 맨 오른쪽 인덱스의 네 가지 매개변수를 수신합니다.
    메서드 본체는 다음 순환 확장 및 역추적을 위해 준비됩니다. 먼저 재귀가 확장될 때마다 현재 재귀가 사용하는 시퀀스의 중간 값을 가져와야 합니다. 시퀀스를 "중앙화"하려면 이 값이 필요합니다. 이 값을 얻은 후 이 값을 사용하여 순서. 여기서 나눗셈은 자기 자신을 두 번 호출하여 구현하는데 재귀를 할 때마다 시퀀스를 왼쪽과 오른쪽 부분으로 나누고 싶기 때문에 각 재귀는 자신을 두 번 호출해야 합니다(한 나눗셈, 즉 왼쪽과 오른쪽 부분으로 나누어짐). ) , 한 번은 왼쪽에 속하고 한 번은
    오른쪽 에 속합니다 . 이번에는 시퀀스의 왼쪽 부분에 대한 두 번째 호출 입니다 .가장 왼쪽 인덱스의 위치를 ​​중간 값 + 1에 전달하고 다른 매개 변수는 동일하게 유지합니다. 이번에는 시퀀스의 오른쪽 부분입니다. 전체 재귀적 세분화 과정에서 형성된 다양한 숫자들 사이의 관계는 완전한 이진 트리와 같으며 루트는 원래 배열이고 다음 레이어는 원래 배열에 따라 분할되고 다음 레이어는 이에 따라 생성됩니다. 각 레이어의 시퀀스는 하나의 요소만 가지며 왼쪽 부분을 형성하는 재귀는 각 레이어에서 왼쪽 시퀀스를 생성하는 데 사용되고 오른쪽 부분을 형성하는 재귀는 각 레이어에서 오른쪽 시퀀스를 생성하는 데 사용됩니다. 자신을 두 번 호출하는 코드 아래에서 두 개의 배열을 처리하기 위해 호출합니다. 즉, 앞에서 작성한 메소드를 호출하고 왼쪽과 오른쪽에 인덱스, 원래 배열과 임시 배열을 전달합니다.



정렬과 통합을 위한 코드는 모든 배열이 분할된 후에 실행되는 것이 아니라, 일부 위치에서 분할(즉, 재귀적 확장) 및 규칙(즉, 백트래킹과 정렬의 조합)됩니다. 이진 트리를 통해 이해할 수 있는데 여전히 수열의 이진 트리인데 매우 간단합니다. 여기서 어떻게 설명할지 모르겠습니다. 두 숫자의 병합 및 정렬을 수행하고 이 작업은 다음에서 시작됩니다. 트리의 왼쪽. 전체 숫자가 만들어진 후에 숫자의 병합이 시작된다는 의미가 아니라, 가지와 잎의 생성 및 병합이라는 코드 이해와 결합될 수 있습니다
.

암호

이 병합 정렬에 대한 코드는 조금 더 있습니다. 코드를 직접 붙여넣기만 하면 됩니다.

class MergeSortDemo{
    
    
    public void SortDemo(int[] array,int left,int right,int[] temp){
    
    
        if (left<right){
    
      //
            int mid=(left+right)/2;  //获得数列的中间索引值
//            下面俩分别是把数列分为左和右两部分
            SortDemo(array,left,mid,temp); //递归,调用自己,此递归主要处理每段数列左侧的那部分数列。传入中间的索引作为下一次数列的最右侧的值
            SortDemo(array,mid+1,right,temp);  //递归,此递归主要处理每段数列右侧的那部分数列。传入中间的索引作为下一次数列的最左侧的值
            merge(array,left,mid,right,temp);  //此过程是主要在回溯的过程发生作用,每次回溯进入到这里,便进行排序和交换
//            System.out.println("temp+"+Arrays.toString(temp));
        }
    }
    public void merge(int[] array,int left,int mid,int right,int[] temp){
    
    
//        记录索引值
        int i=left;
        int j=mid+1;
        int t=0;

        while(i<=mid&&j<=right){
    
      //比对左右两个有序数列
//            每次先取左侧数列的一个值和右侧数列的值比较,把小的值放在临时数组中,并把索引向后移动
            if (array[i]<=array[j]){
    
    
                temp[t]=array[i];
                t++;
                i++;
            }else {
    
    
                temp[t]=array[j];
                t++;
                j++;
            }
        }

        while (i<=mid){
    
      //如果左侧还剩余有数值,则全部填充到临时数组中
            temp[t]=array[i];
            t++;
            i++;
        }

        while (j<=right){
    
      //若右侧还剩余有数值,则填充到临时数组中
            temp[t]=array[j];
            t++;
            j++;
        }

        t=0;
        int start=left;  //此值,用于记录一个原数列的起始位置,用于用临时数组把原数组中数列的更改
        while (start<=right){
    
      //对原数列进行更改
            array[start]=temp[t];
            t++;
            start++;
        }
    }
}

코드를 보기 위해 위의 설명과 결합

8백만 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
800만 단 1초, 이미 매우 빠르다

모든 코드

다음과 같이:

import java.util.Arrays;

public class MergeSort {
    
    
    public static void main(String[] args) {
    
    
        MergeSortDemo sortDemo=new MergeSortDemo();
        int[] array={
    
    5,12,1,6,78,-5,4};
        int[] temp=new int[array.length];
        sortDemo.SortDemo(array,0,array.length-1,temp);
        System.out.println(Arrays.toString(array));  //这是是测试排序是否正确

        //下面才是八百万个数据测速
        int Array[]=new int[8000000];
        int[] Temp=new int[Array.length];
        for (int i = 0; i < 8000000; i++) {
    
    
            Array[i]= (int) (Math.random()*8000000);
        }
        Long time1=System.currentTimeMillis();
        sortDemo.SortDemo(Array,0,Array.length-1,Temp);
        System.out.println("耗时(毫秒)"+(System.currentTimeMillis()-time1));
    }
}

//归并排序
//总的来看,感觉归并排序好像要简单一些
//思路:采用分治算法,把一个数列分而治之。就是把一个数列先进行分开,再依次对分开的数列进行处理
//大致思路:采用递归,对一个数列进行分,每次把数列分为两部分,一直递归,一直到每个数列仅剩一个数值,然后此时开始回溯
// 回溯过程中对两个数列进行排序和整合,每处理两个相邻的数列就将他们合并,然后再与其他由两个数列合并成的数列进行整合处理
//进一步思路和原理:(下面以从小到大为例)
//  首先我们要知道如何处理两个相邻的数列,这里我们这样做,写一个方法,接收几个参数,包括:此数列最左侧、最右侧、中间值的索引和原数组和一个临时数组
//  这些索引用于从原数组中得到要处理的数列,并可根据中间值,把两个数列分为左右两部分。临时数组用于对两个的合并排序,同时把合并后的数列放在原数组的位置上
//  合并和处理的过程:
//      首先按照中间值的索引得到左右两个数列,设左右数列分别为甲乙,甲数列从最左侧到中间值,乙从中间值+1到最右侧,
//      每次从一个数列中取出一个数据,和另一个数列中的数据依次比较。
//      首先记录下最左侧和最右侧的索引(用于后续把临时数组中的数据放回到数组中)。
//      假设每次从甲中取出一个和乙中数据依次比较,则把小的数据放在临时数组中,并同时更改索引,
//      假设甲中的值小,则把甲中的这个数据放在临时数组中,并把甲当前索引向后移动一位,同时把临时数组当前索引也向后移动一位(为了下次的存放数据)
//      然后再用甲的当前索引所指的数据和乙数列比较,再重复上述操作,直到某个数列空了,则把另一个数列中的剩余数据按照顺序加载临时数组后面
//      这时便得到一个有序数列,再把这个放在临时数组中的数列更换到原数列中的位置处,此时这里便有了一个有序数列,此数列后续会和其他数列再进行比较,再重复上述操作
//      直到整个数据排序完成。
//      注意一点,因为我们对于两个数列的合并处理是从两个仅有一个数值的数列开始的,所以每次参加排序的数列都是有序的,所以其实每次的对两数列的融合排序都是对两个有序数列的联合排序
//  要想非常巧妙的完成对整个数列实现上述的操作,这里便需要一个非常巧妙的递归来对数列进行分组和处理
//  分组和递归回溯:
//      重写一个方法,接收四个参数,分别是元素组,临时数组,最左侧索引,最右侧索引
//      方法体中为接下来的递归的展开和回溯做好准备。首先,每次递归的展开,我们需要得到当前递归所使用的的数列的中间值,我们需要此值对数列进行"中分"
//      得到此值后,我们便利用此值对数列进行分割。
//      这里的分割是借由两次对自身的调用来实现的
//      我们是要每次递归把数列分割为左右两部分,所以每次递归需要调用两次本身(等于一次分割,即分为左右两部分),一次属于左侧,一次属于右侧
//      具体原理:第一次调用自身,我们把该传入最右侧索引的位置传入中间值,其他的参数照旧。此次即为数列分出来的左侧部分
//      第二次调用自身,我们把该传入最左侧索引的位置传入中间值+1,其他的参数照旧。此次即为数列分出来的右侧部分
//      整个递归再分割过程中所形成的的各个数列间的关系就像完全二叉树,根是原数组,然后根据原数组然后分出下一层,再根据这层再生成下一层,直到每层中的数列都仅有一个元素
//      形成左侧部分的递归用于生成每层中左边的数列,形成右侧部分的递归用于生成每层中右侧的数列。
//  在两次调用自身的代码下面进行调用对两个数列的处理,即调用我们前面写的方法,传入左右中的索引和原数组和临时数组
//  注意一点,这里并不是把数组全部分好才会执行排序和整合部分的代码,他是分(即递归的展开)和治(即回溯和排序组合)有些地方交叉着进行的
//  可以借由二叉树来理解,还是那个数列的二叉树,很简单,这里不知道怎么描述,不描述了,
//  反正就是分的过程中进行二叉树的创建,碰到底了,便进行一次回溯,累计两次便执行一次对俩数列的合并和排序,而且此操作是从树的左侧开始,并不是说数整个构建好后才开始执行数列的合并
//  可以结合代码理解,就是一个树枝叶的创建和枝叶的合并的过程


class MergeSortDemo{
    
    
    public void SortDemo(int[] array,int left,int right,int[] temp){
    
    
        if (left<right){
    
      //
            int mid=(left+right)/2;  //获得数列的中间索引值
//            下面俩分别是把数列分为左和右两部分
            SortDemo(array,left,mid,temp); //递归,调用自己,此递归主要处理每段数列左侧的那部分数列。传入中间的索引作为下一次数列的最右侧的值
            SortDemo(array,mid+1,right,temp);  //递归,此递归主要处理每段数列右侧的那部分数列。传入中间的索引作为下一次数列的最左侧的值
            merge(array,left,mid,right,temp);  //此过程是主要在回溯的过程发生作用,每次回溯进入到这里,便进行排序和交换
//            System.out.println("temp+"+Arrays.toString(temp));
        }
    }
    public void merge(int[] array,int left,int mid,int right,int[] temp){
    
    
//        记录索引值
        int i=left;
        int j=mid+1;
        int t=0;

        while(i<=mid&&j<=right){
    
      //比对左右两个有序数列
//            每次先取左侧数列的一个值和右侧数列的值比较,把小的值放在临时数组中,并把索引向后移动
            if (array[i]<=array[j]){
    
    
                temp[t]=array[i];
                t++;
                i++;
            }else {
    
    
                temp[t]=array[j];
                t++;
                j++;
            }
        }

        while (i<=mid){
    
      //如果左侧还剩余有数值,则全部填充到临时数组中
            temp[t]=array[i];
            t++;
            i++;
        }

        while (j<=right){
    
      //若右侧还剩余有数值,则填充到临时数组中
            temp[t]=array[j];
            t++;
            j++;
        }

        t=0;
        int start=left;  //此值,用于记录一个原数列的起始位置,用于用临时数组把原数组中数列的更改
        while (start<=right){
    
      //对原数列进行更改
            array[start]=temp[t];
            t++;
            start++;
        }
    }
}

직접 삽입 정렬

아이디어 :
버블링과 셀렉션이 합쳐진 느낌이고, 크기를 비교하는 방식은 버블링 같고, 정렬 방식은 셀렉션과 같습니다.
배열(unordered list)의 원소들을 차례대로 꺼내 다른 배열(ordered list)에 순서대로 넣고, 최종적으로 얻은 배열은 ordered 추가 아이디어: 먼저 첫 번째
원소
나누어 낸다. 이때가 있다. 이때 정렬된 목록이라고도 할 수 있는 정렬된 목록에 하나의 숫자만 있는
다음 정렬되지 않은 목록을 루프하고 크기
순서 대로 정렬된 목록에 추가합니다 .
배열에서 작동합니다. 이때 작업이 시작됩니다. 선택과 버블링처럼 보입니다.
우선 현재 배열을 두 부분으로 간주합니다.처음에는 배열 헤드 요소를 하나의 목록으로 간주합니다.이 때 순서가 지정된 목록이고 그 다음 배열의 뒤쪽입니다. 의 요소는 정렬되지 않은 목록인 목록으로 사용됩니다.
배열의 첨자 1이 있는 요소부터 시작하여 정렬되지 않은 목록을 반복하기 시작합니다.
정렬되지 않은 목록에서 요소(A)를 꺼낼 때마다 먼저 변수를 사용하여 값을 저장한 다음 정렬된 목록의 요소와 비교합니다.비교 순서는 정렬된 목록의 끝에서
작은 것부터 큰 것까지 순서대로 배열하고 싶다고 가정하면
, A보다 큰 요소를 만났을 때 배열에서 A의 위치가 배열에서
이때 A보다 큰 요소의 값이 되도록 하자. 두 개의 동일한 요소(앞에서 B, 뒤에서 C라고 하며 둘 다 A보다 큰 값입니다. C의 위치는 원래 A의 위치입니다. 이때 그와 시퀀스 목록은 인접한)
그런 다음 이때 B 앞에 요소가 있는지 판단하고 그렇지 않은 경우 B의 위치를 ​​A로 바꾸십시오. 이번에는 정렬이 끝났습니다. 즉, 최소값이 배열의 앞에 배치됩니다.
있다면 이 요소와 A의 크기를 판단하여 A보다 크면 B의 위치를 ​​이 요소로 교체합니다. 즉, 위의 작업을 반복하고 작으면 B의 위치를 ​​A로 교체합니다. , 이 정렬을 종료합니다
. 발생한 요소가 A보다 작으면 이 정렬을 중지합니다. 다음 정렬을 입력하고 정렬되지 않은 목록에서 다음 요소를 선택하고 비교하십시오.
이때 A의 위치는 정렬된 리스트의 끝이 되고, 정렬되지 않은 리스트에서 선택된 데이터가 정렬되지 않은 리스트의 선두가 된다.

직접 삽입 정렬의 구현

여기에 이미지 설명 삽입
코드가 많지 않음

8만 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
솔직히 아직 교환 횟수가 꽤 많아서 정렬 속도가 만족스럽지 않고 선택 정렬보다 조금 빠를 뿐인데 여기서는 80,000개의 데이터만 정렬하고 필요한 속도는 1초 정도로 빠르다.

모든 코드

다음과 같이

import java.util.Arrays;

public class InsertSort {
    
    
    public static void main(String[] args) {
    
    
        InsertSortDemo sortDemo=new InsertSortDemo();
        int[] array=new int[80000];
        for (int i = 0; i < 80000; i++) {
    
    
            array[i]= (int) (Math.random()*80000);
        }
        int[] array1={
    
    1,23,45,2,-2,48};
        System.out.println(Arrays.toString(sortDemo.SortDemo(array1)));  //这个仅是为了证明排序的正确性

        Long time1=System.currentTimeMillis();
        sortDemo.SortDemo(array); //这里是排序八十万个数据
        Long time2=System.currentTimeMillis();
        System.out.println("排序80000个数据耗时"+(time2-time1));
    }
}
//        思路:
//        感觉像是冒泡和选择的结合体,比较大小的方式像冒泡,排序的方式像选择。
//        依次取出数组(无序列表)中的元素,按照顺序放在另外一个数组(有序列表)中,最后得到的那个数组就是有序的
//        进一步的思路:
//        首先把第一个元素分出去,此时有序列表中只有一个数字,此时也可以称其为有序列表
//        然后循环无序列表,依次按大小顺序加入到有序列表中
//        大致思路:
//        我们可以不用写两个数组,可以直接在当前的数组中进行操作,此时的操作就开始像选择和冒泡了
//        首先我们把当前数组看作两部分,一开始,数组头元素单独作为一个列表,此时他即为有序列表,然后数组后面的元素作为一个列表,即为无序列表
//        我们开始循环后面的无序列表,从数组下标为1的元素开始。
//        每从无序列表中取出一个元素(甲),我们先用一个变量来保存一下他的值,然后将其与有序列表中的元素进行比较,比较顺序是从有序列表的尾部向有序的头部,
//        假设我们是想按照从小到大的顺序排,
//        当遇到比甲大的元素的时候,则让甲在数组中所在的位置变成这个比甲大的元素的值
//        此时数组中便有了两个相同的元素(在前面的称为乙,在后面的称为丙,这俩都是那个比甲大的值。丙所在的位置就是原甲所在的位置,此时他与有序列表相邻),
//        然后此时判断乙前面是否还有元素,若没有,则把乙所在的位置换成甲,此次排序结束,即最小值放在了数组的最前面
//        若有,则判断这个元素和甲的大小,若大于甲,则把乙所在的位置换成这个元素,即重复上面的操作,若小于,则把乙所在的位置换成甲,结束此次排序
//        当遇到的元素比甲小,则停止此次排序。进入下一次排序,选择无序列表中下一个元素,进行比较。
//        此时甲所在的位置便成为了有序列表的尾部,此时从无序列表中选择的这个数据就成了此无序列表的头
class InsertSortDemo{
    
    
    public int[] SortDemo(int[] array){
    
    
        for (int i = 1; i < array.length; i++) {
    
       //此for循环用于依次获得无序列表中的元素
            int n=array[i];   //定义一个变量保存每次从无序列表中得到的数值(即待插入的数值)
            int m=i-1;  //保存待插入数值的前一个数值的索引,用于后面的while循环对有序列表的遍历
            while (m>=0&&n<array[m]){
    
      //array[m]表示有序列表中的值,m的值后面会更新。m作为有序列表中的索引,此处也能用它来判断有序列表是否遍历结束
//                循环的结束条件:当有序列表中的某值比取出的值小,或有序列表遍历完成
                array[m+1]=array[m];  //更新有序列表的值,此处即得到两个相同的值
                m--;  //我们对有序列表的遍历是从尾到头的,所以此处用自减。当索引变成-1则表示有序列表遍历结束
            }
            if (m!=i-1) {
    
      //这里可以做一个判断,如果未进入到while循环中,即直接取出的元素大于有序列表尾的元素,则不用进行下面的赋值操作。他的标志是m是否变化
                array[++m] = n;  //将相同的两个值靠前面的那个值给换成我们从无序列表中取出的元素。因为上面的m是先自减,再判断循环条件是否成立,所以此处的m需要做一次自增
            }
        }
        return array;
    }
}

힐 정렬

Hill Sorting은 Insertion Sorting의 최적화된 Sorting으로,
기존 Insertion Sorting을 기반으로 Step Size가 1이 아니라는 개념을 추가하여 데이터 교환 횟수를 줄이고, 실제로 Sorting 속도를 높였다
. 크기? 정렬에서 단계 크기는 매번 시퀀스에서 선택한 두 데이터 사이의 색인 간 차이입니다.
힐정렬 : 힐정렬은 정렬과정에서 교환되는 횟수를 줄이기 위해 삽입정렬을 기본으로 삽입정렬을 최적화하는 것으로 삽입정렬의 최적화이다 삽입정렬만 알면 어렵지 않다 르.

기존 삽입정렬의 단점과 힐정렬의 개선점 : 배열을 1,2,3,4,5,6,0으로 정렬하면 마지막 0을 여러번 옮겨야 하므로 시간낭비 힐소팅은 이 과정을 줄일 수 있으니 여기서 힐은 빅보스가 될 만하다 며 스텝 사이즈가 1이 아닌
. 정렬되지 않은 목록에서 선택한 값을 정렬된 목록과 비교하는 경우 정렬된 목록의 순회에 일부 변경이 이루어지므로 순회하는 동안 인덱스가 매번 증가하고 변경되어 비교 프로세스의 속도를 높이고 이동 횟수를 줄입니다
. : 원래 삽입 정렬에 for 루프가 있습니다. 이 루프는 단계 크기를 설정하는 데 사용되며, for의 초기 값은 원래 시퀀스의 길이를 2로 나눈 다음 업데이트입니다. of i는 i=i/2이고, 이 for 루프는 삽입 정렬입니다. 차이점은 삽입 정렬에서 각 시퀀스의 증분 단계 크기는 외부에서 매번 얻은 단계 크기
원칙 설정 삽입 정렬의 단계 크기는 원래의 숫자 시퀀스와 같으며 매번 일정하게 여러 위치를 추출하고 이 위치의 데이터에 대해 삽입 정렬을 수행합니다. 매번 그리는 위치의 수는 지난번에 그린 위치의 2배로 증가하고 마지막까지 비교를 위해 각 위치를 추출해야 한다. 즉, 단계 크기가 1이 되면 마지막 삽입 정렬을 수행하고, 그런 다음 정렬 마침

암호

여기에 이미지 설명 삽입삽입 정렬과 비교할 때 for 루프가 하나 더 있고 여기에는 약간의 세부 사항이 있으므로 여기서는 언급하지 않겠습니다.

800만 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
속도는 여전히 매우 빠르며 원래 삽입 정렬보다 훨씬 빠릅니다.

총 코드

다음과 같이:

import java.util.Arrays;

public class ShellSort {
    
    
    public static void main(String[] args) {
    
    
        int[] array={
    
    1,5,2,8};
        ShellSortDemo sortDemo=new ShellSortDemo();
        System.out.println(Arrays.toString(sortDemo.SortDemo(array)));
        int[] Array=new int[8000000];
        for (int i = 0; i < 8000000; i++) {
    
    
            Array[i]= (int) (Math.random()*8000000);
        }
        Long time1=System.currentTimeMillis();
        sortDemo.SortDemo(Array);
        Long time2=System.currentTimeMillis();
        System.out.println("排序8000000个数据耗时(毫秒)"+(time2-time1));
    }
}
//希尔排序:希尔排序就是在插入排序的基础上,对插入排序进行优化,使其排序过程中的交换次数减少
//  是对插入排序的一种优化,只要懂得插入排序,这个就并不难勒
//  原插入排序的一些缺点:若数组1,2,3,4,5,6,0排序,则最后一个0需要移动很多次,浪费时间,希尔排序可以减少这个过程
//  所以这里希尔这个人不愧是大佬,引入了步长不为1的情况,简单来说,就是让每次的i递增更多一点
//  思路:对插入排序引入步长不为1的情况,再用从无序列表中选出的数值与有序列表比对的过程中,对有序列表的遍历做出一些改变,
//  让遍历时候的索引每次递增更改一下,加快对比过程,减少移动次数
//  大致思路:在原插入排序外套一个for循环,这个循环用于设置步长,for中i初始值是原数列长度除以二,随后i的更新是i=i/2
//          然后在这个for循环中就是一个插入排序,不同的是,插入排序中每次序列的递增的步长是外面每次得到的步长
//  原理:一开始,将插入排序的步长设置高一点,就等于在原数列中,每次均匀的抽出几个位置,对在这些位置上的数据进行插入排序。
//    每次抽的位置数量在以二倍于上一次抽的位置数量的速度递增,直到最后,每个位置都需要抽出进行比较,即步长变为一的时候,进行最后一个插入排序,然后排序完成
class ShellSortDemo{
    
    
    public int[] SortDemo(int[] array){
    
    
        int num,k;
        for (int i = array.length/2; i >0; i/=2) {
    
    
            for (int j =  i; j < array.length; j++) {
    
    
                num=array[j];
                for (k = j; k >=i&&num<array[k-i]; k-=i) {
    
    
                    array[k]=array[k-i];
                }
                if (k!=j){
    
    
                    array[k]=num;
                }
            }
        }
        return array;
    }
}

기수 정렬

카디널리티 정렬은 "버킷 방법"이라고도 합니다.
이름에서 알 수 있듯이 키 값의 부분 정보를 통해 정렬할 요소를 일부 "버킷"에 할당하여 정렬 효과를 얻습니다. 기수 정렬 방법은 안정적인 정렬 방법이며 시간 복잡도는 O( nlog ®m) 여기서 r은 사용된 기준이고 m은 힙 수입니다.어느 시점에서 카디널리티 정렬 방법의 효율성은 다른 안정성 정렬 방법보다 높습니다.

버킷 정렬의 고급 버전인 카디널리티 정렬(다음은 작은 것에서 큰 것으로 정렬하는 예입니다.)
아이디어 : 먼저 각각 0-9를 나타내는 10개의 버킷을 정의한 다음 일련의 숫자를 트래버스합니다. 각 숫자 얼굴 숫자의 값, 이 값에 따라 각 숫자는 다른 컨테이너에 배치됩니다.각 순회 후 데이터는 0-9의 버킷 순서로 제거되어 원래 숫자의 데이터를 대체합니다. 통과한 라운드 수는 시퀀스 자릿수에서 최대값입니다.

추가 생각 : 버킷이라고 하는 10개의 컨테이너를 정의하고 각 버킷을 0에서 9까지 표시합니다. 그런 다음 시퀀스에서 최대값을 찾고 n으로 표시되는 자릿수를 가져옵니다. 그런 다음 배열 탐색을 시작합니다.

  1. 먼저 각 값의 한 자릿수를 취하여 0에서 9까지 어떤 값인지 확인한 다음 이 값을 정의된 버킷에 넣습니다. 버킷의 후속 값은 이전 값의 앞이 아니라 이전 값의 뒤에 있어야 합니다. 값. 각 버킷의 표시에 따라 0에서 9까지 순회 라운드 후 각 버킷의 데이터를 순차적으로 꺼내고(각 버킷의 헤드부터 시작) 원래 번호 순서대로 넣습니다(모든 데이터를 교체). 원래 번호 시퀀스) 데이터)
  2. 그런 다음 후속 순회를 입력합니다. 이때 이전 순회에서 얻은 시퀀스를 순회합니다. 후속 순회는 데이터의 상위 자리에 해당하는 값을 빼낸 다음 이 시퀀스의 데이터를 업데이트하고 루프를 계속 반복합니다. , n회 순회, 즉 최대값 순회 의 자릿수 . 그런 다음 숫자의 시퀀스는 이때 순서가 지정된 숫자 시퀀스입니다. 일부 값의 숫자가 충분하지 않으면 0을 사용하여 보충하고 0을 사용하여 높은 숫자를 대체하십시오. 예를 들어 1은 00001로 간주할 수 있습니다.

원칙 : 정렬하는 동안 자릿수가 같은 값을 발견하면 다르게 표시된 버킷을 사용하여 더 큰 값을 나중에 버킷에 배치하여 동일한 자릿수 값의 순서를 보장할 수 있습니다. 다른 자릿수, 낮은 자릿수 값의 경우 0을 사용하여 자릿수를 보완한 다음 높은 자릿수가 0이 됩니다. 높은 자릿수 값과 비교하여 낮은 자릿수 값을 보장할 수 있습니다. 서로 다른 비트 값 사이의 질서를 보장하는 전면 Bucket에 있을 수 있습니다.

암호

class RadixSortDemo{
    
    
    public void SortDemo(int[] array){
    
    
        int[][] bucket = new int[10][array.length];  //定义10个桶,防止数据溢出,每个桶的深度都需要定义为原数列的长度
        int[] bucketLength=new int[10];  //存储每个桶中有效数据的数量,用于后续对每个桶取数据和放数据的时候使用,
        // 防止在取数据的时候对桶进行过多的遍历和取出错误数据和浪费时间

//        下面代码用于得到数列中最大值的位数
        int maxLength=array[0];
        for (int i = 1; i < array.length; i++) {
    
    
            if (array[i]>maxLength){
    
    
                maxLength=array[i];
            }
        }
        maxLength=(maxLength+"").length();



//        核心部分
        for (int i=0,n=1;i<maxLength;i++,n=n*10){
    
      //i用于控制对数列的遍历次数,n用于后续获得每个数值对应位数的值

//          将元素放入对应的桶中
            for (int j = 0; j < array.length; j++) {
    
      //遍历数列
                int DigitElement=array[j]/n%10;  //取出对应位数的值
//                bucketLength[DigitElement]表示的是DigitElement桶中的元素个数,在此处可以作为每个桶中应该添加元素的位置
//                DigitElement的作用就是表示每个元素对用的桶
                bucket[DigitElement][bucketLength[DigitElement]]=array[j];  //根据对应的值,把数值放到对应的桶中
                bucketLength[DigitElement]++;   //更新记录每个桶有效长度的数组中的数据
            }


//            把桶中的数据放回到原数列中
            int index=0;  //用于替换原数列,充当索引
            for (int j = 0; j < bucketLength.length; j++) {
    
      //控制遍历每个桶
                if (bucketLength[j]!=0){
    
    
                    for (int k = 0; k < bucketLength[j]; k++) {
    
      //取出桶中的数据
                        array[index++]=bucket[j][k];
                    }
                    bucketLength[j]=0;  //此时,桶中的数据取出完毕,把桶的有效数据的数量设置为0
                }
            }
        }
    }
}

800만 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
0.5초, 초고속

모든 코드

다음과 같이:

import java.util.Arrays;

public class RadixSort {
    
    
    public static void main(String[] args) {
    
    
        int[] array={
    
    1,45,4,2,98,6,42,0};
        RadixSortDemo sortDemo=new RadixSortDemo();
        sortDemo.SortDemo(array);
        System.out.println(Arrays.toString(array));

        int Array[]=new int[8000000];
        for (int i = 0; i < 8000000; i++) {
    
    
            Array[i]= (int) (Math.random()*8000000);
        }
        Long time1=System.currentTimeMillis();
        sortDemo.SortDemo(Array);
        System.out.println("耗时(毫秒)"+(System.currentTimeMillis()-time1));
    }
}
//基数排序,桶排序的进阶版(下面按照从小到大排序来进行示例)
//思路:先定义十个桶,分别表示0-9,然后对数列进行遍历,每次遍历对每个数取其最后面位数的数值,按照此值对每个数放在不同容器中,
//      每轮遍历结束便按照桶从0-9的顺序取出数据替换原数列中的数据,遍历的轮数是数列中最大值的位数
//进一步的思路:定义十个容器,称为桶,从0到9标记每个桶。然后从数列中找到最大值,得到其的位数,记为n。然后开始对数列进行遍历。
//      首先,取每个数值的个位数,看是0到9中的哪个值,然后把这个数值放在定义好的桶中,后续放入桶的数值需要在前面数值的后面,不能放在之前数值的前面
//      一轮遍历结束后,按照从每个桶的标记,从0到9,依次取出每个桶中的数据(从每个桶的头部开始取)放在原数列中(替换掉原数列中的所有数据)
//      然后进入之后的遍历,此时便遍历上次遍历得到的数列,之后的遍历是取出数据的更高位数对应的值,然后再更新这个数列的数据,一直循环,
//      遍历n次,即遍历最大值的位数的次数。然后此时数列便为有序数列。当某些数值他的位数不够,则用0来凑,用0来替代他的高位。比如1,可以当作00001
//原理:排序中,若遇到同位数的值,则可以通过不同标记的桶,把更大的值放在更后面的桶中,保证同位数的值之间的有序性
//    对有不同位数的值,低位数的值,他通过0来补充位数,然后他的高位则变成了0,和高位数的值比较,便能保证低位数的值能处在前面的桶,保证了不同位数值之间的有序性
class RadixSortDemo{
    
    
    public void SortDemo(int[] array){
    
    
        int[][] bucket = new int[10][array.length];  //定义10个桶,防止数据溢出,每个桶的深度都需要定义为原数列的长度
        int[] bucketLength=new int[10];  //存储每个桶中有效数据的数量,用于后续对每个桶取数据和放数据的时候使用,
        // 防止在取数据的时候对桶进行过多的遍历和取出错误数据和浪费时间

//        下面代码用于得到数列中最大值的位数
        int maxLength=array[0];
        for (int i = 1; i < array.length; i++) {
    
    
            if (array[i]>maxLength){
    
    
                maxLength=array[i];
            }
        }
        maxLength=(maxLength+"").length();



//        核心部分
        for (int i=0,n=1;i<maxLength;i++,n=n*10){
    
      //i用于控制对数列的遍历次数,n用于后续获得每个数值对应位数的值

//          将元素放入对应的桶中
            for (int j = 0; j < array.length; j++) {
    
      //遍历数列
                int DigitElement=array[j]/n%10;  //取出对应位数的值
//                bucketLength[DigitElement]表示的是DigitElement桶中的元素个数,在此处可以作为每个桶中应该添加元素的位置
//                DigitElement的作用就是表示每个元素对用的桶
                bucket[DigitElement][bucketLength[DigitElement]]=array[j];  //根据对应的值,把数值放到对应的桶中
                bucketLength[DigitElement]++;   //更新记录每个桶有效长度的数组中的数据
            }


//            把桶中的数据放回到原数列中
            int index=0;  //用于替换原数列,充当索引
            for (int j = 0; j < bucketLength.length; j++) {
    
      //控制遍历每个桶
                if (bucketLength[j]!=0){
    
    
                    for (int k = 0; k < bucketLength[j]; k++) {
    
      //取出桶中的数据
                        array[index++]=bucket[j][k];
                    }
                    bucketLength[j]=0;  //此时,桶中的数据取出完毕,把桶的有效数据的数量设置为0
                }
            }
        }
    }
}

빠른 정렬

퀵소트의 전체적인 원리는 그리 복잡하지 않은데 구현하기가 좀 번거롭다는 느낌이 듭니다.

아이디어 :
수치 비교 및 ​​교환이기도 한 버블 정렬의 최적화된 버전과 약간 비슷합니다. 여기서는 재귀가 사용됩니다.
전체적인 개념 : 일련의 숫자는 그 중에서 하나의 값을 기준값으로 찾은 다음 그 순서에 있는 다른 데이터를 기준값과 비교하여 기준값보다 큰 값을 기준값 오른쪽에 넣는다. , 기준값보다 작은 값을 왼쪽 기준값 오른쪽에 배치합니다. (어떤 면을 넣을지는 원하는 정렬 순서에 따라 다릅니다). 그런 다음 나눈 후 재귀를 사용하여 표준 값의 왼쪽과 오른쪽에서 위의 작업을 시퀀스에 하나의 요소만 남을 때까지 계속하면 정렬이 완료됩니다.
일반적인 아이디어 : 매번 시퀀스에서 표준 번호를 선택한 다음 표준 번호에 따라 시퀀스를 왼쪽과 오른쪽 부분으로 나눈 다음 왼쪽 및 오른쪽 부분에 대한 표준 번호를 취하여 나누고이 작업을 반복하십시오. 얻은 시퀀스에 요소가 하나만 남을 때까지 이 방법을 반복하면 정렬이 완료됩니다.
추가 아이디어 : (소형에서 대형 정렬을 예로 들면)
퀵 정렬의 비교 및 ​​교환 프로세스: 편의상 비교 프로세스와 교환 프로세스가 아래에 분리되어 있습니다. 실제로 비교는 일반적으로 교환과 교차합니다.

  1. 비교과정 : 정렬하고자 하는 각 시퀀스는 시작위치와 끝위치가 있는데, 시작위치와 끝위치에서 시작하여 데이터와 표준번호를 순차적으로 시퀀스의 중심으로 꺼내어 비교한다. .
    여기서의 비교과정은 십자형으로 이루어지며, 먼저 끝위치에서 데이터를 가져와서 기준숫자와 비교하여 기준숫자보다 크거나 같으면 끝위치를 나타내는 인덱스를 1비트 앞으로 이동시킨다. 여기서는 1씩 감소하며, 기준수보다 작으면 끝 위치부터 비교를 멈추고 데이터 교환을 입력하고 시작 위치부터 비교를 입력한 후 차례로 데이터를 빼서 비교한다. 기준 숫자와 비교하여 기준 숫자보다 작거나 같으면 인덱스를 뒤로 1비트 이동, 즉 1씩 증가시킵니다. 기준 숫자보다 크면 처음부터 비교합니다. 위치가 종료되고 교환이 수행된 후 마지막 끝 위치에서 마지막으로 변경된 인덱스가 변경됨 값이 시작되고 다음 비교 단계로 들어가 위의 비교 과정을 반복하며 비교 종료 조건은 다음과 같습니다
    . 시작 인덱스와 끝 인덱스가 증가하고 다른 인덱스가 감소하면 루프 In 의 어느 레이어에 관계없이 두 개가 만날 때(즉, 두 값이 같을 때) 항상 회의 프로세스가 있을 것입니다. , 이 시퀀스와 이 표준 값의 정렬이 종료됨을 의미합니다.
  2. 교환 과정
    : (여기서 교환 이란 두 개의 값을 교환하는 것이 아니라 하나의 값을 다른 값에 대입하여 두 개의 동일한 값을 얻는 것을 의미) 센터 따라서 효율성을 높이고 교환 횟수를 줄이기 위해 여기의 교환 프로세스도 양쪽에서 중앙으로 수렴됩니다.
    먼저 재귀를 할 때마다 시퀀스의 가장 왼쪽에 있는 첫 번째 데이터를 기준 번호로 선택하고 처음에는 이 데이터를 먼저 저장합니다. 우리의 첫 번째 비교는 종료 인덱스(비교 중에 종료 인덱스가 변경됨)에서 시작하여 종료 인덱스가 나타내는 값이 기준 숫자보다 작을 때 시작 인덱스의 값을 종료 인덱스의 값으로 대체합니다( 종료 인덱스 인덱스의 값은 변경되지 않음) 교환이 종료됩니다.
    그런 다음 시작 인덱스에서 비교값을 입력합니다. 시작 인덱스가 나타내는 값이 기준 수치보다 큰 경우 종료 인덱스가 나타내는 값을 시작 인덱스가 나타내는 값으로 바꿉니다(시작 인덱스 값은 변경되지 않음). . 이것은 실제로 버블링과 유사한 교환 방법입니다. 매번 기준 숫자보다 작은 값과 기준 숫자보다 큰 값을 받고 둘을 교환하는 것이 보장됩니다. 매우 영리한 디자인으로 교환이 완료될 때마다 배열에 두 개의 동일한 값이 있고 왼쪽은 시작 인덱스가 가리키고 오른쪽은 끝 인덱스가 가리킵니다. 시작 인덱스가 끝 인덱스에 도달하면 해당 컬럼의 배열 비교가 완료되었음을 의미하므로 루프가 종료됩니다. 이때 현재 배열은 여전히 ​​동일한 두 개의 값을 갖게 됩니다. 재귀가 끝났습니다. 즉, 이 수열의 처리가 끝난 다음 표준 숫자의 위치에서 분리하고 양쪽에서 차례로 수열을 처리하고 재귀를 사용하여 진행합니다.

여기에 작은 세부 사항이 있습니다 . 이 정렬의 원리만 보면 시작 인덱스와 끝 인덱스가 같을 때 두 개의 동일한 값이 있기 때문에
교환 프로세스에 약간의 문제가 있음을 느낄 수 있습니다.
이 순서대로, 이때 이 두 인덱스가 어떤 값을 가리키는가가 매우 중요한데, 이 두 값이 기준값보다 크다고 가정했을 때 시작과 끝이 나중의 값을 가리키면 기준값이 그들이 가리키는 값에 할당되면 표준 값은 왼쪽에 표준 값보다 큰 값을 가지며 이는 문제를 나타냅니다.
마찬가지로 두 값이 모두 기준값보다 작고, 두 인덱스가 모두 이전 값을 가리키고 있다면 최종적으로 값을 할당한 후에 문제가 발생하겠지만, 이 알고리즘은 매우 영리하므로 두 값이 모두 같으면 기준치보다 작으면 두 지표 모두 전자를 가리킨다. 후자의 경우 두 값이 모두 기준치보다 크면 두 지표 모두 전자를 가리킨다. 추후 해당 위치에서 분석 및 설명

암호

class QuickSortDemo{
    
    

    public int[] SortDemo(int[] array,int start,int end){
    
    
        //每次的递归,就是对一段数列的排序,每次所求的数列即建立在原数组上的,所以这里我们需要设置两个参数,用来设置此数列在数组中的起始索引和结束索引

        if (start>=end) {
    
    
            //这里是设置递归的结束条件,当起始索引大于结束索引,则表示此数列中没有数据(出现这样情况的原因是标准数的一侧没有数据),
//            当结束时索引等于起始索引,则表示此数列中只有一个数据,则不需要排序。
            return array;
        }

            int num=array[start]; //假设我们每次取数列的第一个值为标准数(对于快速排序来说取哪个都行,但是取不同的数值,程序的写法可能需要出现一些更改)

//        记录起始和结束位置
            int right=end;
            int left=start;


//            这里我们下面的解释排除掉数列中除标准数外的所有值都大于标准数的情况,
//            因为当这种情况发生,就不会发生交换,仅仅是遍历一遍,更不会发生数列中出现两个相同值的情况,那就不会出现上面小细节中所说的问题

            while (left!=right){
    
      //这里对数列进行循环,所有循环的结束条件是left==right
                while (left!=right&&num<=array[right]){
    
      //此循环用于从终止索引开始,寻找到一个比标准数小的值
//            下面是对对应小细节问题的解释

                    right--;//当因为right--导致的left==right,则在两个相同的值之间,则两者便都指向靠左侧的那个值
//                    因为left==right而结束的循环,则必定是因为right自减到left,此时则必定是同时指向左侧的那个值

//                    且此时两个相同的值则必定是大于标准值的,为什么?
//                    因为到这里之前,必定会出现赋值操作,一定有一次赋值是把左边大的值赋值到右边小的值上面,所以个相同的值必定是大于标准值的
//                    为什么必定会出现赋值操作,且一定有一次赋值是下面的while后的交换?
//                    因为能到这里,则说明left!=right,若下面的while循环的结束不是因为left==right,则只能是因为,前面有个值大于标准值,此时就会发生交换
                }

                if (left!=right){
    
       //若上面循环的结束不是因为触碰到了所有循环的结束条件(left==right),则进行交换操作(即把一个值赋给另一个值)
//                    当left==right了,则这里的交换便没有意义了
                    array[left]=array[right];
                    //在这之前,两个相同的值是产生于上次下面的交换,则这俩相同的值必定大于标准值
                    // 所以咱们把找到的这个小于标准值的值赋给两个相同的值中左边的那个
                }

                while (left!=right&&num>=array[left]){
    
      //此循环用于从起始索引开始,寻找一个比标准数大的值
//            下面是对对应小细节问题的解释
//                    若能进入到这里,则表示之前必定进行过一次赋值,即两个相同值都小于标准值

//                    当在这里运行的时候left==right,则在两个相同的值之间,则两者便都指向靠右侧的那个值
//                    上面for循环的结束若不是因为left==right,则必定会有赋值,
//                    赋值完成之后,数列中会出现两个相同且比标准值小的数值,在赋值完成后,left都指向左侧那个,right都指向右侧那个,

                    left++;//当因为left++导致的left==right,则在两个相同的值之间,两者便都指向靠右侧的那个值
//                    因为left==right而结束这个while循环,则必定是因为left自增到right,此时则必定是同时指向右侧的那个值
                }

                if (left!=right) {
    
      //若上面循环的结束不是因为触碰到了所有循环的结束条件(left==right),则进行交换操作(即把一个值赋给另一个值)
//                    当left==right了,则这里的交换便没有意义了
                    array[right] = array[left];//把左边大的值赋值到右边小的值上面
                    //在这之前,两个相同的值是产生于上面的交换,则这俩相同的值必定小于标准值
                    // 所以咱们把找到的这个大于标准值的值赋给两个相同的值中右边的那个
                }

            }
            if (array[left] != num) {
    
    
//                若数列中除标准数外的所有值都大于标准数,则上面就不会发生交换,则这里也就没必要进行值的更改
                array[left] = num;
            }
//            把数列分为两部分,进行递归
            SortDemo(array,start,right);  //左侧数列,从数组头开始,到标准值位置
            SortDemo(array,right+1,end);//右侧数列,从标准值位置的下一个数值开始,到数组尾
        return array;
    }
}

800만 데이터 정렬 속도 테스트

여기에 이미지 설명 삽입
거의 1초가 꽤 빠릅니다.

모든 코드

다음과 같이:

import java.util.Arrays;

public class QuickSort {
    
    
    public static void main(String[] args) {
    
    
        int[] array={
    
    56,1,-5,4,3,14,5,0,1,3,12,13,14};
        QuickSortDemo sortDemo=new QuickSortDemo();
        System.out.println(Arrays.toString(sortDemo.SortDemo(array,0,array.length-1)));
        int Array[]=new int[8000000];
        for (int i = 0; i < 8000000; i++) {
    
    
            Array[i]= (int) (Math.random()*8000000);
        }
        Long time1=System.currentTimeMillis();
        sortDemo.SortDemo(Array,0,Array.length-1);
        System.out.println("耗时(毫秒)"+(System.currentTimeMillis()-time1));
    }
}
//    快速排序:
//    思路:
//    有点像是优化版的冒泡排序,也是数值比较,然后交换,这里用到了递归。
//    整体的思路:对于一个数列,从中找一个数值作为标准值,然后用数列中的其他数据与标准值作比较,比标准值大的放到标准值右边,比标准值小的放在标准值的左边。(放到哪边取决于想要的排序的顺序)
//    然后分好后,用递归对标准值左右两边的数列持续上述操作,直到数列中仅剩一个元素,此时即排序完成。
//    大致思路:
//    每次从数列中选取一个标准数,然后根据标准数把数列分为左右两部分,然后再分别对左右两部分取标准数,再分,再对分出来的数列重复这样的操作,就这样递归
//    直到得到的数列仅剩一个元素,此时即排序完成。
//    进一步思路:(以从小到大排序作为例子)
//    快速排序的比较和交换过程:为了方便,下面把比较的过程和交换的过程分开讲。实际中,比较一般是和交换是交叉的
//      比较过程:每次需要排序的数列有一个起始位置和终止位置,我们这里需要分别从起始位置和结束位置开始,向数列中心依次取出数据和标准数作比较。
//          这里的比较过程是交叉进行的,先从终止位置依次取数据与标准数比较,若比标准数大或相等,则让表示终止位置的索引向前移动一位,此处即自减1,
//          若比标准数小,则终止这个从终止位置开始的比较,进入一次数据的交换,进入从起始位置开始的比较,同样是依次取出数据,与标准数进行比较
//          当比标准数小或相等,则此索引向后移动一位,即自增1,若比标准数大,则终止这个从起始位置开始的比较,进入一次交换,
//          然后,再从上次终止位置的索引最后经过变化后的值开始,进入下一轮的比较,重复上面的比较过程,
//          比较的终止条件:由于起始索引和终止索引一个一直自增一个一直自减,总会有一个相遇的过程,
//          无论在哪层循环中,当两者相遇(即两个值相等的时候),则表示以此数列和以此标准值的此排序结束
//      交换过程:(此处的交换并不是说是把两个值互换,而是把一个值赋给另一个值,然后会得到两个相同的值)
//          由于这个比较过程是由两侧向中心收束的所以为了提高效率,减少交换的次数,这里的交换过程也是由两侧向中心收束的
//          首先,我们每次递归都是选取数列的最左侧的第一个数据作为标准数的,一开始,我们先保存这个数据。
//          我们第一次的比较是从终止索引开始的(终止索引会在比较过程中发生变化),当终止索引代表的值比标准数小
//          则将起始索引的值换成终止索引的值(终止索引的值不变),这个交换便结束。
//          然后进入从起始索引的比较,当起始索引代表的值比标准数大,则将终止索引代表的值换成起始索引代表的值(起始索引的值不变)
//          这样起始也是一种交换的方法,和冒泡类似。能保证每次得到一个把比标准数小的数值和一个比标准数大的数值,然后两者交换
//          非常巧妙的一种设计,每次交换完成,数列中都会有两个相同的值,且左侧的由起始索引指向,右侧的由终止索引指向。当起始索引碰到终止索引,则表示这一列数组比对完成
//          即,循环结束,此时,当前数列还是会有两个相同的值,我们只需要把起始和终止所指向的那个数换成标准数,则表示此次递归结束,即此数列处理结束
//    然后从标准数的位置分开,依次处理两边的数列,用递归来进行递进

//    这里有一个小细节:
//    如果仅看此排序的原理,会觉得在交换的过程有一点问题,
//    因为,当在起始索引等于终止索引的时候,此数列中会有两个相同的值,此时这俩索引指向哪个值便十分关键,
//    假设这俩值都大于标准值,若这起始和终止都指向靠后面的那个值,则若把标准值赋给他们指向的值,则此时标准值的左边有了大于标准值的值,这便出现了问题
//    同理,若俩值都小于标准值,且俩索引都指向前面的,则最后赋完值后,就有了问题
//    但是这个算法非常巧妙,使若俩值都小于标准值,俩索引都指向后面的,若俩值都大于标准值,俩索引都指向前面的。后面到对应的位置进行分析解释
class QuickSortDemo{
    
    

    public int[] SortDemo(int[] array,int start,int end){
    
    
        //每次的递归,就是对一段数列的排序,每次所求的数列即建立在原数组上的,所以这里我们需要设置两个参数,用来设置此数列在数组中的起始索引和结束索引

        if (start>=end) {
    
    
            //这里是设置递归的结束条件,当起始索引大于结束索引,则表示此数列中没有数据(出现这样情况的原因是标准数的一侧没有数据),
//            当结束时索引等于起始索引,则表示此数列中只有一个数据,则不需要排序。
            return array;
        }

            int num=array[start]; //假设我们每次取数列的第一个值为标准数(对于快速排序来说取哪个都行,但是取不同的数值,程序的写法可能需要出现一些更改)

//        记录起始和结束位置
            int right=end;
            int left=start;


//            这里我们下面的解释排除掉数列中除标准数外的所有值都大于标准数的情况,
//            因为当这种情况发生,就不会发生交换,仅仅是遍历一遍,更不会发生数列中出现两个相同值的情况,那就不会出现上面小细节中所说的问题

            while (left!=right){
    
      //这里对数列进行循环,所有循环的结束条件是left==right
                while (left!=right&&num<=array[right]){
    
      //此循环用于从终止索引开始,寻找到一个比标准数小的值
//            下面是对对应小细节问题的解释

                    right--;//当因为right--导致的left==right,则在两个相同的值之间,则两者便都指向靠左侧的那个值
//                    因为left==right而结束的循环,则必定是因为right自减到left,此时则必定是同时指向左侧的那个值

//                    且此时两个相同的值则必定是大于标准值的,为什么?
//                    因为到这里之前,必定会出现赋值操作,一定有一次赋值是把左边大的值赋值到右边小的值上面,所以个相同的值必定是大于标准值的
//                    为什么必定会出现赋值操作,且一定有一次赋值是下面的while后的交换?
//                    因为能到这里,则说明left!=right,若下面的while循环的结束不是因为left==right,则只能是因为,前面有个值大于标准值,此时就会发生交换
                }

                if (left!=right){
    
       //若上面循环的结束不是因为触碰到了所有循环的结束条件(left==right),则进行交换操作(即把一个值赋给另一个值)
//                    当left==right了,则这里的交换便没有意义了
                    array[left]=array[right];
                    //在这之前,两个相同的值是产生于上次下面的交换,则这俩相同的值必定大于标准值
                    // 所以咱们把找到的这个小于标准值的值赋给两个相同的值中左边的那个
                }

                while (left!=right&&num>=array[left]){
    
      //此循环用于从起始索引开始,寻找一个比标准数大的值
//            下面是对对应小细节问题的解释
//                    若能进入到这里,则表示之前必定进行过一次赋值,即两个相同值都小于标准值

//                    当在这里运行的时候left==right,则在两个相同的值之间,则两者便都指向靠右侧的那个值
//                    上面for循环的结束若不是因为left==right,则必定会有赋值,
//                    赋值完成之后,数列中会出现两个相同且比标准值小的数值,在赋值完成后,left都指向左侧那个,right都指向右侧那个,

                    left++;//当因为left++导致的left==right,则在两个相同的值之间,两者便都指向靠右侧的那个值
//                    因为left==right而结束这个while循环,则必定是因为left自增到right,此时则必定是同时指向右侧的那个值
                }

                if (left!=right) {
    
      //若上面循环的结束不是因为触碰到了所有循环的结束条件(left==right),则进行交换操作(即把一个值赋给另一个值)
//                    当left==right了,则这里的交换便没有意义了
                    array[right] = array[left];//把左边大的值赋值到右边小的值上面
                    //在这之前,两个相同的值是产生于上面的交换,则这俩相同的值必定小于标准值
                    // 所以咱们把找到的这个大于标准值的值赋给两个相同的值中右边的那个
                }

            }
            if (array[left] != num) {
    
    
//                若数列中除标准数外的所有值都大于标准数,则上面就不会发生交换,则这里也就没必要进行值的更改
                array[left] = num;
            }
//            把数列分为两部分,进行递归
            SortDemo(array,start,right);  //左侧数列,从数组头开始,到标准值位置
            SortDemo(array,right+1,end);//右侧数列,从标准值位置的下一个数值开始,到数组尾
        return array;
    }
}

힙 정렬 후속 보충

Supongo que te gusta

Origin blog.csdn.net/qq_45821251/article/details/120999707
Recomendado
Clasificación