Data Structures and Algorithms Three Sorting

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:
insert image description here

1.1 Introduction to Comparable interface

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;
		}
	}
}

1.2 Bubble sort

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

Requirements:
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.

insert image description here
Bubble sort API design:
insert image description here
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));
	}
}

Time Complexity Analysis of Bubble Sort

Bubble sorting uses a double-layer for loop, and the loop body of the inner loop is the code that actually completes the sorting. Therefore, we analyze the time complexity of bubble sorting, mainly analyzing the execution times of the inner loop body.

In the worst case, that is, if the elements to be sorted are in the reverse order of {6,5,4,3,2,1}, then:
the number of element comparisons is:

 (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

The number of element swaps is:

 (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

The total number of executions is:

 (N^2/2-N/2)+(N^2/2-N/2)=N^2-N;

According to the big O derivation rule, the highest-order item in the function is retained, so the time complexity of the final bubble sort is O(N^2).

1.3 Selection sort

Selection sort is a simpler and more intuitive sorting method.

Requirement:
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

insert image description here

Selection sort API design:
insert image description here

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));
	}
}

Time complexity analysis of selection sorting:
selection sorting uses a double-layer for loop, in which the outer loop completes the data exchange, and the inner loop completes the data comparison, so we count the number of data exchanges and data comparisons respectively:

Number of data comparisons:

 (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

Number of data exchanges:
N-1

time complexity:N^2/2-N/2+(N-1)=N^2/2+N/2-1;

According to the big O derivation rule, the highest order item is retained, and the constant factor is removed, and the time complexity is O(N^2);

1.4 Insertion sort

Insertion sort is a simple, intuitive and stable sorting algorithm.
Insertion sort works a lot like people sort a hand of poker. To start, our left hand is empty and the cards on the table are face down. Then, we take one card from the table at a time and insert it in the correct position in the left hand. To find the correct position of a card, we compare it with each card already in the hand from right to left, as shown in the diagram below:
insert image description here
Requirements:
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;

insert image description here
Insertion sort API design:
insert image description here
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;
	}
}

Time Complexity Analysis of Insertion Sort

Insertion sorting uses a double-layer for loop, and the loop body of the inner loop is the code that actually completes the sorting. Therefore, we analyze the time complexity of insertion sorting, mainly analyzing the execution times of the inner loop body.

In the worst case, that is, the array elements to be sorted are {12,10,6,5,4,3,2,1}, then:
the number of comparisons is:

(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

The number of exchanges is:

(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

The total number of executions is:

(N^2/2-N/2)+(N^2/2-N/2)=N^2-N;

According to the big O derivation rule, the highest-order item in the function is retained, so the time complexity of the final insertion sort is O(N^2).

2. Advanced sorting

We have learned basic sorting before, including bubble sorting, selection sorting and insertion sorting, and analyzed their time complexity in the worst case, and found that they are all O(N^2), and the square order passes us Before learning algorithm analysis, we know that as the input scale increases, the time cost will rise sharply, so these basic sorting methods cannot handle larger-scale problems. Next, we will learn some advanced sorting algorithms to try to reduce the time complexity of the algorithm. degree to the highest order power.

2.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.

Requirement:
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.

insert image description here

Determination of the growth amount h: There is no fixed rule for the value of the growth amount h. Here we adopt the following rules:

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

insert image description here
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));
	}
}

Time complexity analysis of Hill sorting
In Hill sorting, there is no fixed rule for the growth amount h. Many papers have studied various increasing sequences, but none of them can prove that a certain sequence is the best. For Hill The time complexity analysis of sorting is beyond the scope of our course design, so it will not be analyzed here.

We can use post-mortem analysis to compare the performance of Hill sort and insertion sort.
Create a new reverse_shell_insertion.txt file, which stores reverse data from 100000 to 1, and we can complete the test based on this batch of data. The idea of ​​the test: record a time before performing the sorting, and record a time after the sorting is completed. The time difference between the two times is the time-consuming of the sorting.

Hill sort and insertion sort performance comparison test code:

public class SortCompare {
    
    
	public static void main(String[] args) throws Exception{
    
    
		ArrayList<Integer> list = new ArrayList<>();
		//读取reverse_arr.txt文件
		BufferedReader reader = new BufferedReader(new InputStreamReader(new
		FileInputStream("reverse_shell_insertion.txt")));
		String line=null;
		while((line=reader.readLine())!=null){
    
    
			//把每一个数字存入到集合中
			list.add(Integer.valueOf(line));
		}
		reader.close();
		//把集合转换成数组
		Integer[] arr = new Integer[list.size()];
		list.toArray(arr);
		testInsertion(arr);//使用插入排序耗时:20859
		// testShell(arr);//使用希尔排序耗时:31
	}
	
	public static void testInsertion(Integer[] arr){
    
    
		//使用插入排序完成测试
		long start = System.currentTimeMillis();
		Insertion.sort(arr);
		long end= System.currentTimeMillis();
		System.out.println("使用插入排序耗时:"+(end-start));
	}
	
	public static void testShell(Integer[] arr){
    
    
		//使用希尔排序完成测试
		long start = System.currentTimeMillis();
		Shell.sort(arr);
		long end = System.currentTimeMillis();
		System.out.println("使用希尔排序耗时:"+(end-start));
	}
}

Through testing, it is found that when dealing with large batches of data, the performance of Hill sort is indeed higher than that of insertion sort.

2.2 Merge sort

2.2.1 Recursion

Before formally learning recursive sorting, we have to learn about recursive algorithms.
Definition:
When defining a method, calling the method itself inside the method is called recursion

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

Function:
It usually converts a large and complex problem layer by layer into a smaller-scale 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.

Note:
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 memory overflow.

insert image description here
Requirement:
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);
	}
}

2.2.2 Merge sort

Merge sort 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.

Requirements:
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.

insert image description here
Merge sort API design:

insert image description here
Merge principle:
insert image description here
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));
	}
}

Merge sort time complexity analysis:

Merge sort is the most typical example of divide-and-conquer thinking. In the above algorithm, sort a[lo...hi], first divide it into a[lo...mid] and a[mid+1...hi] two parts, They are sorted separately through recursive calls, and finally the sorted sub-arrays are merged into the final sorted result. The exit of this recursion is that if an array can no longer be divided into two sub-arrays, then merge will be executed for merging, and the size of elements will be judged for sorting when merging.
insert image description here

Use a dendrogram to describe the merge. If an array has 8 elements, it will be divided by 2 each time to find the smallest sub-array. The total number of splits is 3, so the log8tree has 3 layers. Then from top to bottom The k layer has 2^k sub-arrays, the length of each array is 2^(3-k), and merging requires at most 2^(3-k)comparisons. Therefore, the number of comparisons for each layer is 2^k * 2^(3-k)=2^3, then the total of the 3 layers is 3*2^3.

Assuming that the number of elements is n, then the number of splits using merge sorting is log2(n), so there are a total of log2(n) layers, then use log2(n) to replace the number of 3 layers above, and the final 3*2^3merge The time complexity of sorting is: log2(n)* 2^(log2(n))=log2(n)*n, according to the big O derivation rule, ignoring the base, the time complexity of the final merge sort is O(nlogn);

Disadvantages of merge sort:
It needs to apply for additional array space, resulting in increased space complexity, which is a typical operation of exchanging space for time.

Merge sort and Hill sort performance test:
We can know from the test that the performance of Hill sort is due to insertion sort. Now that we have learned merge sort, which one is more efficient between merge sort and Hill sort? We use the same test method to complete the performance comparison between the two sorting algorithms.

reverse_arr.txt file, which stores the reverse data from 1000000 to 1, we can complete the test based on this batch of data. The idea of ​​the test: record a time before performing the sorting, and record a time after the sorting is completed. The time difference between the two times is the time-consuming of the sorting.

Hill sort and insertion sort performance comparison test code:

public class SortCompare {
    
    
	public static void main(String[] args) throws Exception{
    
    
		ArrayList<Integer> list = new ArrayList<>();
		//读取a.txt文件
		BufferedReader reader = new BufferedReader(new InputStreamReader(new
		FileInputStream("reverse_merge_shell.txt")));
		String line=null;
		while((line=reader.readLine())!=null){
    
    
			//把每一个数字存入到集合中
			list.add(Integer.valueOf(line));
		}
		reader.close();
		//把集合转换成数组
		Integer[] arr = new Integer[list.size()];
		list.toArray(arr);
		// testMerge(arr);//使用归并排序耗时:1200
		testShell(arr);//使用希尔排序耗时:1277
	}
	public static void testMerge(Integer[] arr){
    
    
		//使用插入排序完成测试
		long start = System.currentTimeMillis();
		Merge.sort(arr);
		long end= System.currentTimeMillis();
		System.out.println("使用归并排序耗时:"+(end-start));
	}
	public static void testShell(Integer[] arr){
    
    
		//使用希尔排序完成测试
		long start = System.currentTimeMillis();
		Shell.sort(arr);
		long end = System.currentTimeMillis();
		System.out.println("使用希尔排序耗时:"+(end-start));
	}
}

Through testing, it is found that there is not much difference between Hill sort and merge sort when processing large batches of data.

2.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.

Requirement:
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.

insert image description here
Quick sort API design:
insert image description here

Segmentation principle:
The basic idea of ​​dividing 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.

insert image description here
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 difference between quick sort and merge sort:
Quick sort is another divide-and-conquer sorting algorithm, which divides an array into two sub-arrays, and the two parts are sorted independently. Quick sort and merge sort are complementary: merge sort divides the array into two sub-arrays to be sorted separately, and merges the ordered sub-arrays to sort the entire array, while the quick sort method is when both arrays are sorted, The entire array is naturally ordered. In merge sort, an array is divided in half, and the merge call happens before the entire array is processed. In quick sort, the position of the split array depends on the contents of the array, and the recursive call happens after the entire array is processed.

Time complexity analysis of quick sorting:
A splitting of quick sorting starts from both ends and searches alternately until left and right coincide. Therefore, the time complexity of a splitting algorithm is O(n), but the time complexity of the whole quick sorting and The number of splits is related.

Optimal situation: The benchmark number selected for each split just divides the current sequence into equal parts.

The optimal case of quick sorting
insert image description here
If we regard the segmentation of the array as a tree, then the above figure is an illustration of its optimal case, which has been segmented logn times, so the time complexity of quick sorting in the optimal case is O(nlogn);

Worst case: The reference number selected for each split is the largest or smallest number in the current sequence, which makes each split have a subgroup, so a total of n times has to be split, so, in the worst case, The time complexity of quick sort is O(n^2);

Worst case quicksort:
insert image description here

Average case: The reference number selected for each split is not the maximum and minimum values, nor the median value. In this case, we can also use mathematical induction to prove that the time complexity of quick sorting is O(nlogn), due to mathematical induction The law has a lot of mathematics-related knowledge, which is easy to confuse us, so the time complexity of the average case will not be proved here.

2.4 Stability of sorting

The 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 sort, it can be guaranteed that the A element is still in front of the B element. It can be said This algorithm is stable.

insert image description here
The significance of stability:
If a set of data only needs to be sorted once, stability is generally meaningless. If a set of data needs to be sorted multiple times, stability is meaningful. For example, the content to be sorted is a group of commodity objects. The first sorting is sorted according to price from low to high, and the second sorting is sorted according to sales volume from high to low. If the stability algorithm is used for the second sorting, the same sales volume can be achieved. Objects are still displayed in the order of high and low prices, and only objects with different sales volume need to be reordered. In this way, the original meaning of the first sorting can be maintained, and the system overhead can be reduced.

The first time is sorted by price from low to high:
insert image description here
the second time is sorted by sales from high to low:
insert image description here
the 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/qq_33417321/article/details/121956391