一、分治法
- 分治法:将原问题划分成若干个规模较小而结构与原问题一致的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
- 容易确定运行时间,是分支算法的优点之一。
- 分治模式在每一层递归上都有三个步骤:
1、分解:将原问题分解成一系列子问题;
2、解决:递归地解决各子问题。若子问题足够小,则直接有解;
3、合并:将子问题的结果合并成原问题的解。
分治法的关键点:
- 原问题可以一直分解为形式相同子问题,当子问题规模较小时,可自然求解,如一个元素本身有序
- 子问题的解通过合并可以得到原问题的解
- 子问题的分解以及解的合并一定是比较简单的,否则分解和合并所花的时间可能超出暴力解法,得不偿失
二、快速排序算法
- 分解:数组A [p……r] 被划分为两个子数组A [p……q-1] 和A [q+1,r] ,使得A [q] 为大小居中的数,左侧A [p……q-1] 中的每个元素都小于等于它,而右侧A [q+1,r] 中的每个元素都大于等于它。其中计算下标q也是划分过程的一部分。
- 解决:通过递归调用快速排序,对子数组A [p……q-1] 和A [q+1,r] 进行排序
- 合并:因为子数组都是原址排序的,所以不需要合并,数组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] + " ");
}
}
}