Detailed Explanation of Six Sorting Algorithms of Data Structure

insert image description here


1. Simple sorting

In our program, sorting is a very common requirement. Some data elements are provided and these data elements are sorted according to certain rules. For example, query some orders, sort by the date of the order; another example, query some commodities, sort according to the price of the commodity, and so on. So, next we have to learn some common sorting algorithms.

In the java development toolkit jdk, we have provided us with the realization of many data structures and algorithms, such as List, Set, Map, Math, etc., which are all provided in the form of API. The advantage of this method is that it can be written once. Used in many places. We learn from the jdk method and encapsulate the algorithm into a certain class. If this is the case, before we write java code, we need to design the API first, and then implement these APIs after the design is completed.

For example, we first design a set of API as follows:

image-20221109154429978

Then use java code to implement it.

1. Comparable interface introduction

Since we are going to talk about sorting here, we will definitely compare elements, and Java provides an interface Comparable to define sorting rules. Here we make a brief review of the Comparable interface in the form of a case.

need:

  1. Define a student class Student with two attributes of age and name username, and provide comparison rules through the Comparable interface;
  2. Define the test class Test, and define the test method Comparable getMax(Comparable c1, Comparable c2) in the test class Test to complete the test
//学生类
public class Student implements Comparable<Student>{
    
    
    private String username;
    private int age;
    public String getUsername() {
    
    
        return username;
    }
    public void setUsername(String username) {
    
    
        this.username = username;
    }
    public int getAge() {
    
    
        return age;
    }
    public void setAge(int age) {
    
    
        this.age = age;
    }
    @Override
    public String toString() {
    
    
        return "Student{" +
            "username='" + username + '\'' +
            ", age=" + age +
            '}';
    }
    //定义比较规则
    @Override
    public int compareTo(Student o) {
    
    
        return this.getAge()-o.getAge();
    }
}
//测试类
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Student stu1 = new Student();
        stu1.setUsername("zhangsan");
        stu1.setAge(17);
        Student stu2 = new Student();
        stu2.setUsername("lisi");
        stu2.setAge(19);
        Comparable max = getMax(stu1, stu2);
        System.out.println(max);
    }
    //测试方法,获取两个元素中的较大值
    public static Comparable getMax(Comparable c1,Comparable c2){
    
    
        int cmp = c1.compareTo(c2);
        if (cmp>=0){
    
    
            return c1;
        }else{
    
    
            return c2;
        }
    }
}

2. Bubble sort

Bubble Sort is a relatively simple sorting algorithm in the field of computer science.

need:

Before sorting: {4,5,6,3,2,1}

After sorting: {1,2,3,4,5,6}

Sorting principle:

  1. Compare adjacent elements. If the previous element is greater than the latter, swap the positions of the two elements.

  2. Do the same for each pair of adjacent elements, from the first pair at the beginning to the last pair at the end. Finally, the element in the last position is the maximum value.

image-20221109173506296

Bubble sort API design:

image-20221109173617387

Code implementation of bubble sort:

//排序代码
public class Bubble {
    
    
    
//对数组a中的元素进行排序

    public static void sort(Comparable[] a){
    
    
        for(int i=a.length-1;i>0;i--){
    
    
            for (int j = 0; j <i; j++) {
    
    
                if (greater(a[j],a[j+1])){
    
    
                    exch(a,j,j+1);
                }
            }
        }
    }
    
//比较v元素是否大于w元素

    private static boolean greater(Comparable v,Comparable w){
    
    
        return v.compareTo(w)>0;
    }
    
//数组元素i和j交换位置

    private static void exch(Comparable[] a,int i,int j){
    
    
        Comparable t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
//测试代码
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Integer[] a = {
    
    4, 5, 6, 3, 2, 1};
        Bubble.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

The time complexity of bubble sort isO(N^2)

3. Selection sort

need:

Before sorting: {4,6,8,7,9,2,10,1}

After sorting: {1,2,4,5,7,8,9,10}

Sorting principle:

  1. During each traversal, it is assumed that the element at the first index is the minimum value, and it is compared with the values ​​at other indexes in turn. If the value at the current index is greater than the value at some other index, it is assumed that some other The indexed value is the minimum value, and finally the index where the minimum value is located can be found

  2. Swap the values ​​at the first index and the index at which the smallest value is located

image-20221109191758118

Selection sort API design:

image-20221109192014319

Code implementation of selection sort:

public class Selection {
    
    
    /*
对数组a中的元素进行排序
*/
    public static void sort(Comparable[] a){
    
    
        for (int i=0;i<=a.length-2;i++){
    
    
            //假定本次遍历,最小值所在的索引是i
            int minIndex=i;
            for (int j=i+1;j<a.length;j++){
    
    
                if (greater(a[minIndex],a[j])){
    
    
                    //跟换最小值所在的索引
                    minIndex=j;
                }
            }
            //交换i索引处和minIndex索引处的值
            exch(a,i,minIndex);
        }
    }
    /*
比较v元素是否大于w元素
*/
    private static boolean greater(Comparable v,Comparable w){
    
    
        return v.compareTo(w)>0;
    }
    /*
数组元素i和j交换位置
*/
    private static void exch(Comparable[] a,int i,int j){
    
    
        Comparable t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
//测试代码
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Integer[] a = {
    
    4,6,8,7,9,2,10,1};
        Selection.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

The time complexity isO(N^2)

4. Insertion sort

need:

Before sorting: {4,3,2,10,12,1,5,6}

After sorting: {1,2,3,4,5,6,10,12}

Sorting principle:

  1. Divide all elements into two groups, sorted and unsorted;

  2. Find the first element in the unsorted group and insert it into the sorted group;

  3. Traverse the sorted elements in reverse, and compare them with the elements to be inserted in turn, until an element is found that is less than or equal to the element to be inserted, then put the element to be inserted at this position, and move the other elements backward one bit;

image-20221109192418406

Insertion sort code implementation:

public class Insertion {
    
    
    /*
对数组a中的元素进行排序
*/
    public static void sort(Comparable[] a){
    
    
        for (int i=1;i<a.length;i++){
    
    
            //当前元素为a[i],依次和i前面的元素比较,找到一个小于等于a[i]的元素
            for (int j=i;j>0;j--){
    
    
                if (greater(a[j-1],a[j])){
    
    
                    //交换元素
                    exch(a,j-1,j);
                }else {
    
    
                    //找到了该元素,结束
                    break;
                }
            }
        }
    }
    /*
比较v元素是否大于w元素
*/
    private static boolean greater(Comparable v,Comparable w){
    
    
        return v.compareTo(w)>0;
    }
    /*
数组元素i和j交换位置
*/
    private static void exch(Comparable[] a,int i,int j){
    
    
        Comparable t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}

The time complexity isO(N^2)

2. Advanced sorting

1. Hill sort

Hill sorting is a type of insertion sorting, also known as " shrinking incremental sorting ", which is a more efficient and improved version of the insertion sorting algorithm.

When learning about insertion sorting, we will find a very unfriendly thing. If the sorted grouping elements are {2,5,7,9,10} and the unsorted grouping elements are {1,8}, then The next element to be inserted is 1. We need to exchange positions with 10, 9, 7, 5, and 2 in order to complete the real insertion. Each exchange can only exchange with adjacent elements Location. Then if we want to improve efficiency, the intuitive idea is that one exchange can put 1 in a higher position. For example, one exchange can insert 1 between 2 and 5, so that one exchange of 1 will move forward 5 The location can reduce the number of exchanges. How to realize such a requirement? Next, let's take a look at the principle of Hill sorting.

need:

Before sorting: {9,1,2,5,7,4,8,6,3,5}

After sorting: {1,2,3,4,5,5,6,7,8,9}

Sorting principle:

  1. Select a growth amount h, and group the data according to the growth amount h as the basis for data grouping;

  2. Complete insertion sorting for each group of data that has been divided into groups;

  3. Decrease the growth amount, the minimum is 1, and repeat the second step.

image-20221109195344071

The determination of the growth amount h: the value of the growth amount h has a fixed rule, here we adopt the following rules:

int h=1
    while(h<5){
    
    
        h=2h+1//3,7
    }
//循环结束后我们就可以确定h的最大值;
h的减小规则为:
    h=h/2

Code implementation of Hill sort:

//排序代码
public class Shell {
    
    
    /*
对数组a中的元素进行排序
*/
    public static void sort(Comparable[] a){
    
    
        int N = a.length;
        //确定增长量h的最大值
        int h=1;
        while(h<N/2){
    
    
            h=h*2+1;
        }
        //当增长量h小于1,排序结束
        while(h>=1){
    
    
            //找到待插入的元素
            for (int i=h;i<N;i++){
    
    
                //a[i]就是待插入的元素
                //把a[i]插入到a[i-h],a[i-2h],a[i-3h]...序列中
                for (int j=i;j>=h;j-=h){
    
    
                    //a[j]就是待插入元素,依次和a[j-h],a[j-2h],a[j-3h]进行比较,如果a[j]小,那么
                    交换位置,如果不小于,a[j]大,则插入完成。
                        if (greater(a[j-h],a[j])){
    
    
                            exch(a,j,j-h);
                        }else{
    
    
                            break;
                        }
                }
            }
            h/=2;
        }
    }
    /*
比较v元素是否大于w元素
*/
    private static boolean greater(Comparable v,Comparable w){
    
    
        return v.compareTo(w)>0;
    }
    /*
数组元素i和j交换位置
*/
    private static void exch(Comparable[] a,int i,int j){
    
    
        Comparable t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
//测试代码
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Integer[] a = {
    
    9,1,2,5,7,4,8,6,3,5} ;
        Shell.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

Note: When dealing with large batches of data, the performance of Hill sort is higher than insertion sort

2. Merge sort

Regarding recursion : When defining a method, calling the method itself inside the method is called recursion. It usually converts a large and complex problem layer by layer into a smaller problem similar to the original problem to solve. The recursive strategy only needs a small number of programs to describe the multiple repeated calculations required in the problem-solving process, which greatly reduces the amount of code in the program.

public void show(){
    
    
    System.out.println("aaaa");
    show();
}

In recursion, you cannot call yourself unlimitedly. There must be boundary conditions to allow the recursion to end, because each recursive call will open up a new space in the stack memory and re-execute the method. If the recursive level is too deep, it is easy to cause Stack overflow.

image-20221109230628135

need:

Please define a method to use recursion to complete the factorial of N;

分析:
1!: 1
2!: 2*1=2*1!
3!: 3*2*1=3*2!
4!: 4*3*2*1=4*3!
...
n!: n*(n-1)*(n-2)...*2*1=n*(n-1)!
所以,假设有一个方法factorial(n)用来求n的阶乘,那么n的阶乘还可以表示为n*factorial(n-1)

Code:

public class Test {
    
    
    public static void main(String[] args) throws Exception {
    
    
        int result = factorial(5);
        System.out.println(result);
    }
    public static int factorial(int n){
    
    
        if (n==1){
    
    
            return 1;
        }
        return n*factorial(n-1);
    }
}

merge sortIt is an effective sorting algorithm based on the merge operation, which is a very typical application of the divide and conquer method. Combine the ordered subsequences to obtain a completely ordered sequence; that is, first make each subsequence in order, and then make the subsequence segments in order. Merging two sorted lists into one sorted list is called a two-way merge.

need:

Before sorting: {8,4,5,7,1,3,6,2}

After sorting: {1,2,3,4,5,6,7,8}

Sorting principle:

  1. Split a set of data as much as possible into two subgroups with equal elements, and continue to split each subgroup until the number of elements in each subgroup after splitting is 1.

  2. Merge two adjacent subgroups into an ordered large group;

  3. Repeat step 2 continuously until there is only one group at the end.

image-20221110001847958

Merge principle:

image-20221110002153799

image-20221110002323876

image-20221110002500330

image-20221110002545660

image-20221110002605827

Merge sort code implementation:

//排序代码
public class Merge {
    
    
    private static Comparable[] assist;//归并所需要的辅助数组
    /*
对数组a中的元素进行排序
*/
    public static void sort(Comparable[] a) {
    
    
        assist = new Comparable[a.length];
        int lo = 0;
        int hi = a.length-1;
        sort(a, lo, hi);
    }
    /*
对数组a中从lo到hi的元素进行排序
*/
    private static void sort(Comparable[] a, int lo, int hi) {
    
    
        if (hi <= lo) {
    
    
            return;
        }
        int mid = lo + (hi - lo) / 2;
        //对lo到mid之间的元素进行排序;
        sort(a, lo, mid);
        //对mid+1到hi之间的元素进行排序;
        sort(a, mid+1, hi);
        //对lo到mid这组数据和mid到hi这组数据进行归并
        merge(a, lo, mid, hi);
    }
    /*
对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
*/
    private static void merge(Comparable[] a, int lo, int mid, int hi) {
    
    
        //lo到mid这组数据和mid+1到hi这组数据归并到辅助数组assist对应的索引处
        int i = lo;//定义一个指针,指向assist数组中开始填充数据的索引
        int p1 = lo;//定义一个指针,指向第一组数据的第一个元素
        int p2 = mid + 1;//定义一个指针,指向第二组数据的第一个元素
        //比较左边小组和右边小组中的元素大小,哪个小,就把哪个数据填充到assist数组中
        while (p1 <= mid && p2 <= hi) {
    
    
            if (less(a[p1], a[p2])) {
    
    
                assist[i++] = a[p1++];
            } else {
    
    
                assist[i++] = a[p2++];
            }
        }
        //上面的循环结束后,如果退出循环的条件是p1<=mid,则证明左边小组中的数据已经归并完毕,如果退出循环的条件是p2<=hi,则证明右边小组的数据已经填充完毕;
            //所以需要把未填充完毕的数据继续填充到assist中,//下面两个循环,只会执行其中的一个
            while(p1<=mid){
    
    
                assist[i++]=a[p1++];
            }
        while(p2<=hi){
    
    
            assist[i++]=a[p2++];
        }
        //到现在为止,assist数组中,从lo到hi的元素是有序的,再把数据拷贝到a数组中对应的索引处
        for (int index=lo;index<=hi;index++){
    
    
            a[index]=assist[index];
        }
    }
    /*
比较v元素是否小于w元素
*/
    private static boolean less(Comparable v, Comparable w) {
    
    
        return v.compareTo(w) < 0;
    }
    /*
数组元素i和j交换位置
*/
    private static void exch(Comparable[] a, int i, int j) {
    
    
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}
//测试代码
public class Test {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Integer[] arr = {
    
    8, 4, 5, 7, 1, 3, 6, 2};
        Merge.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

The time complexity of merge sort isO(nlogn)

Note: There is not much difference between Hill sort and merge sort when processing large batches of data.

3. Quick Sort

Quick sort is an improvement on bubble sort. Its basic idea is: divide the data to be sorted into two independent parts by one-pass sorting, and all the data in one part is smaller than all the data in the other part, and then perform fast processing on the two parts of the data according to this method Sorting, the entire sorting process can be performed recursively, so that the entire data becomes an ordered sequence.

need:

Before sorting: {6, 1, 2, 7, 9, 3, 4, 5, 8}

After sorting: {1, 2, 3, 4, 5, 6, 7, 8, 9}

Sorting principle:

  1. First set a cutoff value, through which the array is divided into left and right parts;

  2. Put the data greater than or equal to the cutoff value on the right side of the array, and put the data smaller than the cutoff value on the left side of the array. At this time, each element in the left part is less than or equal to the cutoff value, and each element in the right part is greater than or equal to the cutoff value;

  3. Then, the left and right data can be sorted independently. For the array data on the left side, a boundary value can be taken to divide this part of the data into left and right parts, and the smaller value is placed on the left side, and the larger value is placed on the right side. The array data on the right can also be processed similarly.

  4. Repeating the above process, we can see that this is a recursive definition. After sorting the left part by recursion, recursively sort the order of the right part. When the data in the left and right parts are sorted, the sorting of the entire array is completed.

image-20221110004034155

Segmentation principle:

The basic idea of ​​​​slicing an array into two sub-arrays:

  1. Find a reference value, and use two pointers to point to the head and tail of the array respectively;

  2. First search for an element smaller than the reference value from the tail to the head, stop when the search is found, and record the position of the pointer;

  3. Then search for an element larger than the reference value from the head to the tail, stop when the search is found, and record the position of the pointer;

  4. Exchange the elements at the current left pointer position and right pointer position;

  5. Repeat steps 2, 3, and 4 until the value of the left pointer is greater than the value of the right pointer and stop.

image-20221110004311623

image-20221110004412512

image-20221110004609583

Quick sort code implementation:

//排序代码
public class Quick {
    
    
    public static void sort(Comparable[] a) {
    
    
        int lo = 0;
        int hi = a.length - 1;
        sort(a, lo,hi);
    }
    private static void sort(Comparable[] a, int lo, int hi) {
    
    
        if (hi<=lo){
    
    
            return;
        }
        //对a数组中,从lo到hi的元素进行切分
        int partition = partition(a, lo, hi);
        //对左边分组中的元素进行排序
        //对右边分组中的元素进行排序
        sort(a,lo,partition-1);
        sort(a,partition+1,hi);
    }
    public static int partition(Comparable[] a, int lo, int hi) {
    
    
        Comparable key=a[lo];//把最左边的元素当做基准值
        int left=lo;//定义一个左侧指针,初始指向最左边的元素
        int right=hi+1;//定义一个右侧指针,初始指向左右侧的元素下一个位置
        //进行切分
        while(true){
    
    
            //先从右往左扫描,找到一个比基准值小的元素
            while(less(key,a[--right])){
    
    //循环停止,证明找到了一个比基准值小的元素
                if (right==lo){
    
    
                    break;//已经扫描到最左边了,无需继续扫描
                }
            }
            //再从左往右扫描,找一个比基准值大的元素
            while(less(a[++left],key)){
    
    //循环停止,证明找到了一个比基准值大的元素
                if (left==hi){
    
    
                    break;//已经扫描到了最右边了,无需继续扫描
                }
            }
            if (left>=right){
    
    
                //扫描完了所有元素,结束循环
                break;
            }else{
    
    
                //交换left和right索引处的元素
                exch(a,left,right);
            }
        }
        //交换最后rigth索引处和基准值所在的索引处的值
        exch(a,lo,right);
        return right;//right就是切分的界限
    }
    /*
数组元素i和j交换位置
*/
    private static void exch(Comparable[] a, int i, int j) {
    
    
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    /*
比较v元素是否小于w元素
*/
    private static boolean less(Comparable v, Comparable w) {
    
    
        return v.compareTo(w) < 0;
    }
}
//测试代码
public class Test {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Integer[] arr = {
    
    6, 1, 2, 7, 9, 3, 4, 5, 8};
        Quick.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

The time complexity isO(nlogn)

4. The stability of sorting

Definition of Stability:

There are several elements in the array arr, among which the A element and the B element are equal, and the A element is in front of the B element. If a sorting algorithm is used to ensure that the A element is still in front of the B element, it can be said that this algorithm is stable. of.

Stability of common sorting algorithms:

  • Bubble sorting: only when arr[i]>arr[i+1], the position of the elements will be exchanged, and the positions will not be exchanged when they are equal, so bubble sorting is a stable sorting algorithm.

  • Selection sorting: Selection sorting is to select the smallest current element for each position, for example, if there is data {5(1), 8, 5(2), 2, 9 }, the smallest element selected in the first pass is 2, so 5 (1) will exchange positions with 2. At this time, 5(1) is behind 5(2), which destroys stability, so selection sorting is an unstable sorting algorithm.

  • Insertion sort: The comparison starts from the end of the ordered sequence, that is, the element to be inserted is compared with the largest one that is already ordered. If it is larger than it, it will be inserted directly behind it, otherwise it will go forward until it is found The position of the insert. If an element equal to the inserted element is encountered, the element to be inserted is placed behind the equal element. Therefore, the order of the equal elements has not changed, and the order from the original unordered sequence is the sorted order, so the insertion sort is stable.

  • Hill sorting: Hill sorting is to insert and sort elements according to different step lengths. Although an insertion sort is stable and will not change the relative order of the same elements, in different insertion sorting processes, the same elements may be in their respective Insertion sort moves, and finally its stability will be disrupted, so Hill sort is unstable.

  • Merge sort: In the process of merging, the merge sort will only exchange positions when arr[i]<arr[i+1]. If the two elements are equal, the positions will not be exchanged, so it will not destroy stability. Merge sort is stable.

  • Quick sort: Quick sort needs a reference value, find an element smaller than the reference value on the right side of the reference value, find an element larger than the reference value on the left side of the reference value, and then exchange these two elements, at this time Destabilization, so quicksort is an unstable algorithm.

Guess you like

Origin blog.csdn.net/CSDN_anhl/article/details/127780938