八十六.快速排序与归并排序(查找与排序(二))——JAVA

查找与排序(一)
查找与排序(三)
查找与排序(四)

一、分治法

  • 分治法:将原问题划分成若干个规模较小而结构与原问题一致的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
  • 容易确定运行时间,是分支算法的优点之一。
  • 分治模式在每一层递归上都有三个步骤:
    1、分解:将原问题分解成一系列子问题;
    2、解决:递归地解决各子问题。若子问题足够小,则直接有解;
    3、合并:将子问题的结果合并成原问题的解。

分治法的关键点:

  • 原问题可以一直分解为形式相同子问题,当子问题规模较小时,可自然求解,如一个元素本身有序
  • 子问题的解通过合并可以得到原问题的解
  • 子问题的分解以及解的合并一定是比较简单的,否则分解和合并所花的时间可能超出暴力解法,得不偿失

二、快速排序算法

  1. 分解:数组A [p……r] 被划分为两个子数组A [p……q-1] 和A [q+1,r] ,使得A [q] 为大小居中的数,左侧A [p……q-1] 中的每个元素都小于等于它,而右侧A [q+1,r] 中的每个元素都大于等于它。其中计算下标q也是划分过程的一部分。
  2. 解决:通过递归调用快速排序,对子数组A [p……q-1] 和A [q+1,r] 进行排序
  3. 合并:因为子数组都是原址排序的,所以不需要合并,数组A [p……r] 已经有序
QuickSort(A, p, r)
if p<r
   q = Partition(A, p, r)   //主要实现划分这一部分
   QuickSort(A, p, q-1)
   QuickSort(A, q+1, r)

三、快速排序的划分算法
1、一遍单向扫描法:

  • 一遍扫描法的思路是,用两个指针将数组划分为三个区间
  • 扫描指针左边是确认小于等于主元的
  • 扫描指针到某个指针中间为未知的,因此我们将第二个指针称为未知区间末指针,末指针的右边区间为确认大于主元的元素

在这里插入图片描述

import java.util.Scanner;

public class LianXi {
    
    
	/*QuickSort
	 * quickSort(A,p,r)
	 *    if(p<r)
	 *      q = partition(A, p, r)
	 *      quickSort(A, p, q-1)
	 *      quickSort(A, q+1, r)
	 *      
	 * partition(A, p, r):
	 *     pivot = A[p]
	 *     sp = p + 1     //扫描指针
	 *     bigger = r     //右侧指针
	 *     while(sp<=bigger):
	 *         if(A[sp]<=pivot)  //扫描元素小于主元,左指针向右移动
	 *            sp++
	 *         else
	 *           swap(A, sp, bigger) //扫描元素大于主元,二指针的元素交换,右指针左移
	 *           bigger--
	 *           
	 *      swap(A, p, bigger)    //结束循环后,主元与右侧指针需要交换
	 *      return bigger
	 * */
	
	public static void quickSort(int []A, int p, int r){
    
    
		if(p<r){
    
    
			int q = partition(A, p, r);
			quickSort(A, p, q-1);
			quickSort(A,q+1,r);
		}
	}
	
	public static int partition(int[]A, int p, int r){
    
    
		int pivot = A[p];    //选中位数最好,这里没有选
		int sp = p + 1;
		int bigger = r;
		while(sp<=bigger){
    
    
			if(A[sp]<=pivot){
    
    
				sp++;
			}
			else{
    
    
				swap(A,sp,bigger);
				bigger--;
			}
		}
		swap(A, p, bigger);   
		return bigger;
	}

	public static void swap(int[] A, int sp, int bigger) {
    
    
		int temp = A[sp];
		A[sp] = A[bigger];
		A[bigger] = temp;		
	}
	
	public static void main(String[] args){
    
    
		Scanner in = new Scanner(System.in);
		int N = in.nextInt();
		int []A = new int[N];
		for(int i = 0; i<N; i++){
    
    
			A[i] = in.nextInt();
		}
		System.out.println("初始数组为:");
		for(int i = 0; i<N; i++){
    
    
			System.out.print(A[i] + " ");
		}
		System.out.println("\n");
		quickSort(A,0,N-1);    //这里取了第一个元素为主元,应该取中位数
		System.out.println("排序后数组为:");
		for(int j = 0; j<N; j++){
    
    
			System.out.print(A[j] + " ");
		}
	}
}

在这里插入图片描述
2、双向扫描法
双向扫描法的思路是,头尾指针往中间扫描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换,继续扫描,直到左侧无大元素,右侧无小元素。

import java.util.Scanner;

public class LianXi {
    
    
	/*QuickSort
	 * quickSort(A,p,r)
	 *    if(p<r)
	 *      q = partition2(A, p, r)
	 *      quickSort(A, p, q-1)
	 *      quickSort(A, q+1, r)
	 *      
	 * partition2(A, p, r):
	 *   pivot = A[p]
	 *   left = p + 1
	 *   right = r
	 *   while(left<=right):
	 *   //left不停往右走,直到遇到大于主元的元素
	 *        while(left<=right && A[left]<=pivot) left++ //循环退出时,left一定是指向第一个大于大于主元的位置
	 *        while(left<=right && A[right]>pivot) right-- //循环退出时,right一定是指向最后一个小于等于主元的位置
	 *        if(left<right)
	 *           swap(A,left,right)
	 *           
	 *   //while退出时,两者交错,且right指向的是最后一个小于等于主元的位置,也就是主元应该呆的位置
	 *   swap(A, p, right)
	 *   return right
	 *   
	 * */
	
	public static void quickSort(int []A, int p, int r){
    
    
		if(p<r){
    
    
			int q = partition2(A, p, r);
			quickSort(A, p, q-1);
			quickSort(A, q+1, r);
		}
	}
	
	public static int partition2(int[]A, int p, int r){
    
    
		int pivot = A[p];
		int left = p + 1;
		int right = r;
		while(left <= right){
    
    
			while(left <= right && A[left] <= pivot){
    
    
				left++;
			}
			while(left <= right && A[right] > pivot){
    
    
				right--;
			}
			if(left < right)
				swap(A, left, right);
		}
		swap(A, p, right);
		return right;
	}
		
	public static void swap(int[] A, int left, int right) {
    
    
		int temp = A[left];
		A[left] = A[right];
		A[right] = temp;		
	}
	
	public static void main(String[] args){
    
    
		Scanner in = new Scanner(System.in);
		int N = in.nextInt();
		int []A = new int[N];
		for(int i = 0; i<N; i++){
    
    
			A[i] = in.nextInt();
		}
		System.out.println("初始数组为:");
		for(int i = 0; i<N; i++){
    
    
			System.out.print(A[i] + " ");
		}
		System.out.println("\n");
		quickSort(A,0,N-1);        //这里取了第一个元素为主元,应该取中位数
		System.out.println("排序后数组为:");
		for(int j = 0; j<N; j++){
    
    
			System.out.print(A[j] + " ");
		}
	}
}

在这里插入图片描述
3、三指针分区法
① 如果数组中存在很多与主元等值的元素,那么在对主元左、右侧进行快速排序时,可以不用对那些与主元等值的元素进行排序
② 增设一个等值指针,来标记第一个与主元相等的元素,最终将区域划分为三部分:
1.左侧为所有小于主元的元素
2.中间为所有等于主元的元素
3.右侧为所有大于主元的元素
在这里插入图片描述

import java.util.Scanner;

public class LianXi {
    
    
	/*QuickSort
	 * quickSort(A,p,r)
	 *    if(p<r)
	 *      q = partition3(A, p, r)
	 *      quickSort(A, p, q-1)
	 *      quickSort(A, q+1, r)
	 *      
	 * partition3(A, p, r):
	 *   pivot = A[p]
	 *   sp = p + 1
	 *   equal = sp
	 *   bigger = r
	 *   //当sp扫描到的数字小于主元,则下标为sp和equal的需要交换数据,这样就又将小于主元的放在一起了,然后sp和equal都要自增
		 //当sp扫描到的数字等于主元,直接将sp自增
		 //当sp扫描到的数字大于主元,就将小标为sp和bigger上的数据交换,bigger再自减(和单向扫描分区法处理一样)
	 *   while(sp<=bigger):
	 *       if(A[sp] < pivot) 
	 *			swap(array, equal, sp);
	 *			equal++;
	 *			sp++;
	 *		
	 *		else if(A[sp] == num) 
	 *			sp++;
	 *		
	 *		else if(A[sp] > num) 
	 *			swap(A, sp, bigger);
	 *			bigger--;
	 *  
	 *   swap(A, p, bigger)
	 *   return bigger
	 *   
	 * */
	
	public static void quickSort(int []A, int p, int r){
    
    
		if(p<r){
    
    
			int q = partition3(A, p, r);
			quickSort(A, p, q-1);
			quickSort(A, q+1, r);
		}
	}
	
	public static int partition3(int[]A, int p, int r){
    
    
		int pivot = A[p];
		int sp = p + 1;
		int equal = sp;
		int bigger = r;
		while(sp<=bigger){
    
    
			if(A[sp] < pivot){
    
    
				swap(A, equal, sp);
				sp++;
				equal++;
			}
			else if(A[sp] == pivot){
    
    
				sp++;
			}
			else if(A[sp] > pivot){
    
    
				swap(A, sp, bigger);
				bigger--;
			}
		}
		swap(A, p, bigger);
		return bigger;
	}
		
	public static void swap(int[] A, int left, int right) {
    
    
		int temp = A[left];
		A[left] = A[right];
		A[right] = temp;		
	}
	
	public static void main(String[] args){
    
    
		Scanner in = new Scanner(System.in);
		int N = in.nextInt();
		int []A = new int[N];
		for(int i = 0; i<N; i++){
    
    
			A[i] = in.nextInt();
		}
		System.out.println("初始数组为:");
		for(int i = 0; i<N; i++){
    
    
			System.out.print(A[i] + " ");
		}
		System.out.println("\n");
		quickSort(A,0,N-1);        //这里取了第一个元素为主元,应该取中位数
		System.out.println("排序后数组为:");
		for(int j = 0; j<N; j++){
    
    
			System.out.print(A[j] + " ");
		}
	}
}

在这里插入图片描述
四、快速排序优化
1、三点中值法


public class LianXi {
    
    
	
	public static void quickSort(int []A, int p, int r){
    
    
		if(p<r){
    
    
			int q = partition2(A, p, r);
			quickSort(A, p, q-1);
			quickSort(A, q+1, r);
		}
	}
	
	public static int partition2(int[]A, int p, int r){
    
    
		//优化,在p,r,mid之间,选一个中间值作为主元
		int midIndex = p + ((r - p) >> 1);  //中间下标
		int midValueIndex = -1;             //中值的下标
		if(A[p]<=midIndex && A[p]>=A[r]){
    
    
			midValueIndex = p;
		}
		else if(A[r]<=midIndex && A[r]>=A[p]){
    
    
			midValueIndex = r;
		}
		else{
    
    
			midValueIndex = midIndex;
		}
		swap(A, p, midValueIndex);
		int pivot = A[p];
		int left = p + 1;
		int right = r;
		while(left <= right){
    
    
			while(left <= right && A[left] <= pivot){
    
    
				left++;
			}
			while(left <= right && A[right] > pivot){
    
    
				right--;
			}
			if(left < right)
				swap(A, left, right);
		}
		swap(A, p, right);
		return right;
	}
	
	//交换函数	
	public static void swap(int[] A, int left, int right) {
    
    
		int temp = A[left];
		A[left] = A[right];
		A[right] = temp;		
	}
	
	//生成随机数函数
	public static int [] gennerateArray(int len ,int min, int max){
    
    
		int []arr = new int[len];
		for(int i = 0; i<len; i++){
    
    
			arr[i] = (int)(Math.random() * (max-min+1) + 1);
		}
		return arr;
	}
	
	public static void main(String[] args){
    
    
		int []A = gennerateArray(10,1,10);
		System.out.println("初始数组为:");
		for(int i = 0; i<A.length; i++){
    
    
			System.out.print(A[i] + " ");
		}
		System.out.println("\n");
		quickSort(A,0,A.length-1);
		System.out.println("排序后数组为:");
		for(int j = 0; j<A.length; j++){
    
    
			System.out.print(A[j] + " ");
		}
	}
}

在这里插入图片描述
2、待排序列表较短时(>=8),用插入排序


public class LianXi {
    
    
	
	public static void quickSort(int []A, int p, int r){
    
    
		if(p<r){
    
    
			//待排序个数小于等于8的时候,插入排序
			if(p-r+1<=0){
    
    
				insertsort(A,p,r);
			}
			else{
    
    
				int q = partition2(A, p, r);
				quickSort(A, p, q-1);
				quickSort(A, q+1, r);
			}
		}
	}
	
	public static int partition2(int[]A, int p, int r){
    
    
		int pivot = A[p];
		int left = p + 1;
		int right = r;
		while(left <= right){
    
    
			while(left <= right && A[left] <= pivot){
    
    
				left++;
			}
			while(left <= right && A[right] > pivot){
    
    
				right--;
			}
			if(left < right)
				swap(A, left, right);
		}
		swap(A, p, right);
		return right;
	}
	
	//插入排序函数
	 static void Sort(int []arr){
    
    
		insertsort(arr, 0, arr.length-1);
	}
	public static void insertsort(int []arr, int low, int hight){
    
    
		for(int j = low + 1; j<=hight; j++){
    
    
			int x = arr[j];
			int index = j - 1;
			while(index>=low && x<arr[index]){
    
    
				arr[index+1] = arr[index];
				index--;
			}
			arr[index+1] = x;
		}
	}
	
	//交换函数	
	public static void swap(int[] A, int left, int right) {
    
    
		int temp = A[left];
		A[left] = A[right];
		A[right] = temp;		
	}
	
	//生成随机数函数
	public static int [] gennerateArray(int len ,int min, int max){
    
    
		int []arr = new int[len];
		for(int i = 0; i<len; i++){
    
    
			arr[i] = (int)(Math.random() * (max-min+1) + 1);
		}
		return arr;
	}
	
	public static void main(String[] args){
    
    
		int []A = gennerateArray(10,1,10);
		System.out.println("初始数组为:");
		for(int i = 0; i<A.length; i++){
    
    
			System.out.print(A[i] + " ");
		}
		System.out.println("\n");
		quickSort(A,0,A.length-1);
		System.out.println("排序后数组为:");
		for(int j = 0; j<A.length; j++){
    
    
			System.out.print(A[j] + " ");
		}
	}
}

在这里插入图片描述

五、归并排序

  • 归并排序算法完全依照了分治模式
    1、分解:将 n 个元素分成各含 n / 2 个元素的子序列;
    2、解决:对两个子序列递归地排序;
    3、合并:合并两个已排序的子序列以得到排序结果

  • 和快速排序不同的是
    1、归并的分解较为随意
    2、重点是合并

  • 算法描述
    归并操作的工作原理如下:
    1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
    2、设定两个指针,最初位置分别为两个已经排序序列的起始位置
    3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
    4、重复步骤3直到某一指针超出序列尾
    5、将另一序列剩下的所有元素直接复制到合并序列尾

import java.util.Scanner;

public class LianXi {
    
    
	/*
	 *    MergerSort
	 *       mergeSort(A, p ,r){
	 *           if(p<r){
	 *              mid = p + ((r - p) >> 1)
	 *              mergeSort(A, p, mid);   //对左侧排序
	 *              mergeSort(A, mid+1, r); //对右侧排序
	 *              merge(A, p, mid, r)     //合并
	 *           }
	 *       }
	 *       helper = [A.length]
	 *       merge(A, p, mid, r){
	 *          // 先把A的数据拷贝到help中
	 *          copy(A, p, helper, p, r-p+1);
	 *          left = p //左侧队伍的头部指针,指向待比较的元素
	 *          right = mid + 1  //右侧队伍的头部的头部指针,指向待比较的元素
	 *          current = p //原数组的指针,指向待填入数据的位置
	 *          
	 *          while(left<=mid && right<=r){
	 *               if(helper[left]<=helper[right]){
	 *                   A[current] = helper[left]
	 *                   current++
	 *                   left++
	 *               }
	 *               else{
	 *                   A[current] = helper[right]
	 *                   current++
	 *                   right++
	 *               }
	 *          }
	 *          
	 *          //左指针可能没到头;右边指针没到头也没关系
	 *          while(left<=mid){
	 *             A[current] = helper[left]
	 *             current++
	 *             left+++
	 *          }
	 *       }
	 * 
	 * */
	 public static int []helper;
	 
	 public static void mergeSort(int[] arr){
    
    
		 helper = new int[arr.length];
		 mergeSort(arr, 0 , arr.length - 1);
	 }
	 
	 public static void mergeSort(int []A, int p, int r){
    
    
		 if(p<r){
    
    
			 int mid = p + ((r - p) >> 1);
			 mergeSort(A, p, mid);   
			 mergeSort(A, mid+1, r); 
			 merge(A, p, mid, r);    
		 }
	 }
	 
	 public static void merge(int []A, int p, int mid, int r){
    
    
		 System.arraycopy(A, p, helper, p, r - p + 1);
		 int left = p;
		 int right = mid + 1;
		 int current = p;
		 
		 while(left<=mid && right<=r){
    
    
			 if(helper[left] <= helper[right]){
    
    
				 A[current] = helper[left];
				 current++;
				 left++;
			 }
			 else{
    
    
				 A[current] = helper[right];
				 current++;
				 right++;
			 }
		 }
		 
		 while(left <= mid){
    
    
			 A[current] = helper[left];
			 current++;
			 left++;
		 }
	 }
	 
	 public static void main(String[] args){
    
    
		 Scanner in = new Scanner(System.in);
		 int N = in.nextInt();
		 int [] A = new int[N];
		 for(int i = 0; i<N; i++){
    
    
			 A[i] = in.nextInt();
		 }
		 System.out.println("初始数组为:");
		 for(int i = 0; i<N; i++){
    
    
			 System.out.print(A[i] + " ");
		 }
		 System.out.println("\n");
		 mergeSort(A);
		 System.out.println("归并排序后,数组为: ");
		 for(int i = 0; i<N; i++){
    
    
			 System.out.print(A[i] + " ");
		 }
	 }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/JiangYu200015/article/details/113475405