套路算法--算法碎块(第二弹)

各种繁杂,但总是容易忽视,优秀的代码碎块。

优秀的算法总是让人惊艳,到底是何种大牛,何等想法,竟然能实现了如此的数字或是字符处理,更是能实现了那种总是正常,但是仔细想来却是匪夷所思的目的。

但是往往优秀的算法的根基,有一些则是是算法碎块的堆砌,在真正踏上了算法学习的道路上,我们还需要准备些真正的有用,实在的工具。

而不让我们在追求算法的道路上,遭遇拦路虎,而无工具,只能赤手空拳,绞尽脑汁去硬拼,那可不安全咯。

为此,特地做一部分的算法碎块整理,以便能在记忆过后,能对应相应的算法想到了解决方法,从而一块块的处理得到完整完好的解答。

本文涉及内容:
1.交换两个数字位置的两种方法
2.返回整数的符号(正数或0返回1,负数返回0)
3.常见排序重要的实现代码块
4.归并排序中合并核心代码块
***引入知识:如何去估计判断一个递归的复杂式子?
5.对于某一个数而言,快速将一组数分成了左边是小于该数的一组,中间是相等该数,右边是大于该数的一组(荷兰国旗问题,注意不保证有序性和稳定性)(可以引申到快速排序哦!)
6.快速排序核心代码块(其实就是上述的荷兰国旗的引申,不过有个地方要变,那就是num的位置,是变成数组的最后一个数字)
7.堆排序(非常重要,所以整体整理记忆)
8.使用有限队列呈现大根堆和小根堆
9.逆序一个非负数(逆序后首部不包含0,也即正常的数字)
10.桶排序(例题讲评)

代码块一 --交换两个数字位置的两种方法

                //交换i j 位置的值,引入一个临时变量
		//交换数组中两个位置的值,这里传入的是下标指针
		int i=3,j=4;
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = arr[i];
================================================================
                //不需要引入临时变量,
		arr[i] = arr[i]^arr[j];
		arr[j] = arr[i]^arr[j];
		arr[i] = arr[i]^arr[j];
===============================================================
                //不使用临时变量的另一种做法
		arr[i] = arr[i] + arr[j];
		arr[j] = arr[i] - arr[j];
		arr[i] = arr[i] - arr[j];

代码块二 --返回整数的符号(正数或0返回1,负数返回0)

        public static int sign(int n) {
		return ((n >> 31) & 1) ^ 1;
	}

代码块三 -- 常见排序重要的实现块

                int[] arr= {1,56,98,12,45,32,61,5,9,10};//example
                //冒泡排序中核心实现块
		for(int end = arr.length-1 ;end > 0; end--) {
			for(int i = 0 ; i <end ;i++) {
				if (arr[i] > arr[i+1]) {
					swap(arr,i,i+1);
				}
			}
		}
                int[] arr= {1,56,98,12,45,32,61,5,9,10};//example
                //选择排序核心块
		for(int i = 0 ;i < arr.length;i++) {
			int minIndex = i;
                        //选择出该后续子数组中的最小值与当前值进行比对选择交换
			for(int j = i+1 ;j <arr.length;j++) {
				minIndex = arr[j]<arr[minIndex]? j:minIndex;
			}
			swap(arr, i, minIndex);
		}
                int[] arr= {1,56,98,12,45,32,61,5,9,10};//example
                //插入排序核心代码块(重要)
		for(int i = 1; i< arr.length ;i++) {
			for(int j = i-1 ;j>=0 && arr[j]>arr[j+1];j--) {
				swap(arr, j, j+1);
			}
		}
//tips:大家可以对比下和选择排序略有不同的是,插入排序只是一次移动一个位置,然后在已经指针移动过了的的位置中,进行比对排序;而对于选择排序而言,它确实移动之后只去看当前位置和后面数组中的数字去比对。

 代码块四 --归并排序中合并块

        public static void merge(int[] arr,int L ,int mid, int R) {
		//L代表数组的最左的位置,R表示数组最右的位置,mid表示要排序的数组分割的重点
		int[] help = new int[R-L+1];
		int i=0;//记录辅助数组的下标
		int p1=L;//记录左分组的下标
		int p2=mid+1;//记录右分组的下标,注意是从mid+1开始
		while(p1 <= mid  && p2<= R) {
			help[i++] = arr[p1] <arr[p2] ? arr[p1++]:arr[p2++];
		}
		//两个分组当中,有且只有一组越界
		while(p1<=mid) {
			help[i++] = arr[p1++];
		}
		while(p2<=mid) {
			help[i++] = arr[p2++];
		}
		//此款的辅助数组已经将已经排序过的左右分组全部按顺序排好序了,
		//只要在覆盖掉原有的数组就完成了归并排序的最重要的部分
		//tips:i在前面已经做为下标出现过的变量,此处可以继续引用
		for(i = 0 ;i <help.length;i++) {
			arr[L+i]=help[i];
		}
	}

 ***引入知识:如何去估计判断一个递归的复杂式子?

由公式

                                                                T(N)=aT(\frac{N}{b}) + O(N^{d})

其中T(N)表示样本量为N的时间复杂度;\frac{N}{b}:子过程的样本量(b是代表分成了几分之几);a表示的是子过程发生的次数;表示O(N^{^{d}})除去了用子过程之外的剩下用去了多少空间或者说空间复杂度.

且有

a) log_{b}a > d ----->O(N^{log_{b}a})

 b) log_{b}a = d ----->O(N^{d} * log N)

 c) log_{b}a < d ----->O(N^{d} )

代码块五 --对于某一个数而言,快速将一组数分成了左边是小于该数的一组,中间是相等该数,右边是大于该数的一组(荷兰国旗问题,注意不保证有序性和稳定性)(可以引申到快速排序哦!)

       public static int[] partition(int[] arr, int L ,int R,int num) {
		//返回的数组是一个二元数组,表示了分割的位置,分别就是小于num以及大于num的位置
		//得到位置的同时,将数组进行了错分,分成了小于num的左边一组,等于num中间一组,大于num的右边一组
		int less = L - 1;//less表示小于num的位置下标
		int more = R + 1; //more表示大于num的位置下标处
		//tips:在这里,将less和more分别都是从界外开始
		
		while (L < more) {
			if (arr[L] < num) {
			//如果一开始某个数小于num,将某个为和L交换
			//(一开始是自己和自己交换)
				swap(arr,++less,L++);
			}else if (arr[L]>num) {
			//如果某个数大于num,则和最右边的数进行交换
				swap(arr,--more,L);
			}else {
			//如果不是大于也不是小于num则移动L下标
			//这里是复用了L作为了数组下标索引
				L++;
			}
		}
		return new int[]{less+1, more-1};
	}

代码块六 --快速排序核心代码块(其实就是上述的荷兰国旗的引申,不过有个地方要变,那就是num的位置,是变成数组的最后一个数字)

        public static int[] partition(int[] arr, int L ,int R) {
		int less = L - 1;
		int more = R ; 
		
		while (L < more) {
			if (arr[L] < arr[R]) {
				swap(arr,++less,L++);
			}else if (arr[L]> arr[R]) {
				swap(arr,--more,L);
			}else {
				L++;
			}
		}
		swap(arr, more, R);
		return new int[]{less + 1, more};
	}

代码块 七 -- 堆排序(非常重要,所以整体整理记忆)

Tips:“优先队列”就是堆解构

public static void heapSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);//这里存储0~i的大根堆
		}
		int size = arr.length;
		swap(arr, 0, --size);
       //请思考,为何这里还要执行以下的操作?
		while (size > 0) {
			heapify(arr, 0, size);
			swap(arr, 0, --size);
		}
	}

	public static void heapInsert(int[] arr, int index) {
		while (arr[index] > arr[(index - 1) / 2]) {
			swap(arr, index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}

	public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}

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

代码块八 --使用有限队列呈现大根堆和小根堆

        //小根堆
        public static class minHeapComparator implements Comparator<Integer> {
		@Override
		public int compare(Integer o1, Integer o2) {
			return o1 - o2;
		}
	}
	
	public static void main(String[] args) {
		PriorityQueue<Integer> minHeap = new PriorityQueue<>(new minHeapComparator());
		//......
	}
       //大根堆
       public static class maxHeapComparator implements Comparator<Integer>{
		@Override
		public int compare(Integer o1, Integer o2) {
			// TODO Auto-generated method stub
			return o2 - o1 ;
		}
	}
	
	public static void main(String[] args) {
		PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new maxHeapComparator());
		//......
	}

 代码块 九-- 逆序一个非负数(不包含0)

                int res = 0;//OriginalNum 表示原始数
		while (OriginalNum != 0) {
			res=OriginalNum%10 +10*res;
			OriginalNum=OriginalNum/10;
		}

代码十 --桶排序(由于样排序较少使用,结合具体的一道代码题进行解释)

题目:给定一个数组,求如果排序之后,相邻的两个数的最大差值。要求时间复杂度为O(N),且要求不能用非基于比较的排序。

 

思路:使用桶排序 N个数,N+1一个桶,根据鸽巢原理,必有一个桶将是空桶。
那么我们只要将所有的数字进行桶的排序,然后扫描所有桶顶的数比较,只要比之差值最大,就是我们找的最大差值。
桶顶数的规则就是,如果当原先的桶中没有数,但是遍历数组的数分到了这个桶,将之放进去。每个桶只保留两个数,就是要么最大,要么最小。也即每次遍历差值,只找前一个的最大值,和后一个的最小值,那么就是最大的差值,而且无遗漏。
        public static int maxGap(int[] nums) {
		if (nums == null || nums.length < 2) {
			return 0;
		}
		int len = nums.length;
		int min = Integer.MAX_VALUE;
		int max = Integer.MIN_VALUE;
		//获得数组最大值最小值,分别作为首尾两个桶
		for (int i = 0; i < len; i++) {
			min = Math.min(min, nums[i]);
			max = Math.max(max, nums[i]);
		}
		//如果间隔差值为0,毫无比较意义
		if (min == max) {
			return 0;
		}
		//定义数组 N+1个桶
		boolean[] hasNum = new boolean[len + 1];
		int[] maxs = new int[len + 1];//获得桶的最大数
		int[] mins = new int[len + 1];//获得桶的最小数
		int bid = 0;
		for (int i = 0; i < len; i++) {
			bid = bucket(nums[i], len, min, max);//获得桶号
			mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i];
			maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i];
			hasNum[bid] = true;
		}
		int res = 0;
		int lastMax = maxs[0];
		int i = 1;
		for (; i <= len; i++) {
			if (hasNum[i]) {
				res = Math.max(res, mins[i] - lastMax);
				lastMax = maxs[i];
			}
		}
		return res;
	}
	
	//获得桶号
	public static int bucket(long num, long len, long min, long max) {
		return (int) ((num - min) * len / (max - min));
	}

未完待续哦~!(*❦ω❦)后续将继续更新总结~

本文为博主原创总结更新,谢绝转载,本文博客地址为:https://blog.csdn.net/mukvintt/article/details/81611184

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值