java线性选择算法模板

线性时间选择

这里介绍算法中的线性选择算法,顺便说一下快排和快速选择算法。

小提示

线性时间选择是快速选择算法的升级版,而快速选择算法又是基于快速排序的

快速排序算法

先来了解快排:快排用了递归,步骤如下:

  1. 选择一个数作为基准(通常是第一个数)
  2. 将大于这个数的数放它右边,小于这个数的放左边
  3. 再对左右区间不断划分,直到每个区间里只有一个数

盗图:在这里插入图片描述
代码:


package src.app;
public class TestJava{
    
    
	
	public static void main(String[] args){
    
    
		int[] arr = {
    
    10,7,2,4,7,62,3,4,2,1,8,9,19};
        sort(arr);
        for (int i = 0; i < arr.length; i++) {
    
    
            System.out.println(arr[i]);
        }
	}
	//快速排序总函数
	public static void sort(int[] nums){
    
    
		sort(nums, 0, nums.length - 1);
	} 
	//快排核心
	public static void sort(int[] nums, int low, int high){
    
    
		if(low >= high) return;
		//划分数组,比主元大的数在它的右边,比主元小的数在它的左边
		//构建分界点索引p
		int p = partition(nums, low, high);
		//划分好了之后,索引为p的数就已经处于他应该处于的位置上了
		//也就是,比nums[p]大的数都已经在nums[p]的右边
		//比nums[p]小的数都已经在nums[p]的左边
		sort(nums, low, p - 1);
		sort(nums, p + 1, high);
	}

	public static int partition(int[] nums, int low, int high){
    
    
		if(low >= high) return low;
		int pivot = nums[low];
		int i = low, j = high + 1;
		while(true){
    
    
			while(nums[++i] < pivot){
    
    
				if(i == high) break;
			}
			while(nums[--j] > pivot){
    
    
				if(j == low) break;
			}
			if(i >= j) break;
			//很好理解,走到了这里,一定会有nums[i] > pivot 以及 nums[j] < pivot
			//为了保证pivot左边全是比她小的数,右边全是比她大的数
			//我们交换此时nums[i]和nums[j]的位置
			// 保证 nums[lo..i] < pivot < nums[j..hi]
			swap(nums, i, j);
		}
		// 将 pivot 值交换到正确的位置
		swap(nums, j, low);
		// 现在 nums[lo..j-1] < nums[j] < nums[j+1..hi]
		return j;
	}

	public static void swap(int[] nums, int i, int	j){
    
    
		int temp = nums[i];
		nums[i] = nums[j];
		nums[j] = temp;
	}
}

ps:java中Array库的sort()函数不仅采用了快速排序算法,也采用了归并排序算法

快速选择算法

快速选择也应用了快排的思想,用了分治,选取第K大的数,其思想是:每次划分之后判断第k个数在左右哪个部分,然后递归对应的部分。
第K大的数,转换成已为升序的数组里的索引就是nums.length - k,快速选择算法的时间复杂度大多情况下为o(n),但最坏情况下为o(n的平方),为了尽可能减少这样的情况发生,通常先将数组打乱。代码如下:

package src.app;

import java.util.Random;

public class TestJava{
    
    
	
	public static void main(String[] args){
    
    
		int[] arr = {
    
    10,7,2,4,7,62,3,4,2,1,8,9,19};
        sort(arr);
		//查找第五大的数
		int kth = findkthLargest(arr, 5);
        for (int i = 0; i < arr.length; i++) {
    
    
            System.out.println(arr[i]);
        }
		System.out.println(kth);
	}
	//快速排序总函数
	public static void sort(int[] nums){
    
    
		sort(nums, 0, nums.length - 1);
	} 
	//快排核心
	public static void sort(int[] nums, int low, int high){
    
    
		if(low >= high) return;
		//划分数组,比主元大的数在它的右边,比主元小的数在它的左边
		//构建分界点索引p
		int p = partition(nums, low, high);
		//划分好了之后,索引为p的数就已经处于他应该处于的位置上了
		//也就是,比nums[p]大的数都已经在nums[p]的右边
		//比nums[p]小的数都已经在nums[p]的左边
		sort(nums, low, p - 1);
		sort(nums, p + 1, high);
	}

	public static int partition(int[] nums, int low, int high){
    
    
		if(low >= high) return low;
		int pivot = nums[low];
		int i = low, j = high + 1;
		while(true){
    
    
			while(nums[++i] < pivot){
    
    
				if(i == high) break;
			}
			while(nums[--j] > pivot){
    
    
				if(j == low) break;
			}
			if(i >= j) break;
			//很好理解,走到了这里,一定会有nums[i] > pivot 以及 nums[j] < pivot
			//为了保证pivot左边全是比她小的数,右边全是比她大的数
			//我们交换此时nums[i]和nums[j]的位置
			// 保证 nums[lo..i] < pivot < nums[j..hi]
			swap(nums, i, j);
		}
		// 将 pivot 值交换到正确的位置
		swap(nums, j, low);
		// 现在 nums[lo..j-1] < nums[j] < nums[j+1..hi]
		return j;
	}

	public static void swap(int[] nums, int i, int	j){
    
    
		int temp = nums[i];
		nums[i] = nums[j];
		nums[j] = temp;
	}
	public static int findkthLargest(int[] nums, int k ){
    
    
		//为防止出现最坏情况导致时间复杂度为n平方的情况,先把数组打乱了再进行快速选择
		shuffle(nums);
		int low = 0, high = nums.length - 1;
		//索引转换
		k = nums.length - k;
		while (low <= high) {
    
    
			int p = partition(nums, low, high);
			if(p < k){
    
    
				//第k大的数在右边
				low = p + 1;
			}
			else if(p > k){
    
    
				//第k大的在左边
				high = p - 1;
			}
			else{
    
    
				//找到第k大的数
				return nums[k];
			}
		}
		return -1;
	}

	//用来打乱的函数
	public static void shuffle(int[] nums){
    
    
		int n = nums.length;
		Random	rand = new Random();
		for (int i = 0; i < n; i++){
    
    
			int r = i + rand.nextInt(n - i);
			swap(nums, i, r);
		}
	}
}

快速选择算法与线性选择算法的关系

线性选择算法改变了快速选择算法的主元选取规则,采用中位数集合的中位数作为主元
算法的思路是:

  1. 首先把数组按5个数为一组进行分组,最后不足5个的忽略。对每组数进行排序(如插入排序)求取其中位数。
  2. 把上一步的所有中位数移到数组的前面,对这些中位数递归调用线性时间选择算法求得他们的中位数。
  3. 将上一步得到的中位数作为划分的主元进行整个数组的划分。
  4. 判断第k个数在划分结果的左边、右边还是恰好是划分结果本身,前两者递归处理,后者直接返回答案

别人的代码

package root;
public class T3_DAC_Liner_SelectN {
    
    
	
	public static void swap(int a[], int i,int j){
    
    
		int temp=a[j];
		a[j] = a[i];
		a[i] = temp;
	}
	//冒泡排序
	public static void bubbleSort(int a[], int l, int r){
    
    
	    for(int i=l; i<r; i++)
			{
    
    
				for(int j=i+1; j<=r; j++)
				{
    
    
					if(a[j]<a[i])swap(a,i,j);
				}
			}
	}
	//递归寻找中位数的中位数
	public static int FindMid(int a[], int l, int r){
    
    
	    if(l == r) return l;
	    int i = 0;
	    int n = 0;
	    for(i = l; i < r - 5; i += 5)
	    {
    
    
	        bubbleSort(a, i, i + 4);
	        n = i - l;
	        swap(a,l+n/5, i+2);
	    }
	  //处理剩余元素
	    int num = r - i + 1;
	    if(num > 0)
	    {
    
    
	        bubbleSort(a, i, i + num - 1);
	        n = i - l;
	        swap(a,l+n/5, i+num/2);
	    }
	    n /= 5;
	    if(n == l) return l;
	    return FindMid(a, l, l + n);
	}
	//进行划分过程
	public static int Partion(int a[], int l, int r, int p){
    
    
	    swap(a,p, l);
	    int i = l;
	    int j = r;
	    int pivot = a[l];
	    while(i < j)
	    {
    
    
	        while(a[j] >= pivot && i < j)
	            j--;
	        a[i] = a[j];
	        while(a[i] <= pivot && i < j)
	            i++;
	        a[j] = a[i];
	    }
	    a[i] = pivot;
	    return i;
	}
	 
	public static int Select(int a[], int l, int r, int k){
    
    
	    int p = FindMid(a, l, r);    //寻找中位数的中位数
	    int i = Partion(a, l, r, p);
	 
	    int m = i - l + 1;
	    if(m == k) return a[i];
	    if(m > k)  return Select(a, l, i - 1, k);
	    return Select(a, i + 1, r, k - m);
	}
	public static void main(String[] args) {
    
    
		int a[]= {
    
    3,0,7,6,5,9,8,2,1,4,13,11,17,16,15,19,18,12,10,14,23,21,
				   27,26,25,29,28,22,20,24,33,31,37,36,35,39,38,32,30,34,43,41,47,46,45,49,
				   48,42,40,44,53,51,57,56,55,59,58,52,50,54,63,61,67,66,65,69,68,62,60,64,
				   73,71,77,76,75,79,78,72,70,74};
		for(int i = 0; i < 80; i++){
    
    
			System.out.println("第"+(i+1)+"小数为: "+Select(a, 0, 79, i+1));
		}
	}
}


遇到的问题:

1.java的静态方法(main函数)不能调用非静态的函数。

解决方法 将所有函数重新命名为静态的或者取得一个全局静态变量,将非静态的函数赋值给这个静态变量,再用main函数用上这个变量。

参考

C/C++实现快排算法
快排亲兄弟:快速选择算法详解
0006算法笔记——【分治法】线性时间选择
算法题04:分治法:求第K小元素(线性时间选择算法)

Guess you like

Origin blog.csdn.net/weixin_46235143/article/details/116937757