常用算法java汇总

常用算法java汇总

最近在面试的时候,面试官都会问到一些算法题,这里我把一些课本上有的(算法导论)算法的java实现版本整理一下,方便自己查阅,如果里面存在错误的话,请及时指正,谢谢。

1:快排

快排的思想是分支,具体介绍请查看别的博客,这里我主要说明两点内容:

1)为什么最后需要把你选择的某个anchor点交换,比如说,我在程序里面使用的输入数组的第一个元素,为什么需要把第一个元素和end指针(end指针用于从后往前找到小于anchor的点)进行交换?因为如果你不进行交换的话,对于一些偏序的情况,如倒序的情况,是不可行的。而且,每次交换元素之后保证了我们每次至少有一个元素是已经排好序的。

2)对于选择第一个元素作为anchor点时,必须首先从后开始往前找小于anchor的点,因为这样保证的end指针找的节点是小于anchor的,可以用于交换。如果反过来,首先从前找到大于anchor的点,那么end指向的点可能大于anchor,就不能用于交换(end到达start处停止循环)

package Algorithm;

public class QuickSort {

	public static void main(String[] args) {
		int arr[] = {10, 7, 8, 9, 1, 5}; 
		//int arr[] = {10, 10, 10, 10, 10}; 
        int n = arr.length; 
  
        QuickSort ob = new QuickSort(); 
        ob.sort(arr, 0, n-1); 
  
        System.out.println("sorted array"); 
        printArray(arr); 

	}
	int partition(int arr[], int low, int high) 
    { 
        int pivot = arr[high];  
        int i = (low-1); // index of smaller element 
        for (int j=low; j<high; j++) 
        { 
            if (arr[j] <= pivot) 
            { 
                i++; 
                int temp = arr[i]; 
                arr[i] = arr[j]; 
                arr[j] = temp; 
            } 
        } 
  
        // swap arr[i+1] and arr[high] (or pivot) 
        // 为什么要进行交换,因为保证第i+1位一定是已经排好序的,然后只需对前面和后面排序即可,不用对该位再进行排序
        int temp = arr[i+1]; 
        arr[i+1] = arr[high]; 
        arr[high] = temp; 
  
        return i+1; 
    } 

	public static int partion(int[] data, int startIndex, int endIndex) {
		int start = startIndex;
		int end = endIndex;
		int pivot  = data[startIndex];
		while(start<end) {
			//不能够从左先找,因为从左先找的话,找到的一定是一个大于pivot的数,因此无法和pivot互换
			while(data[end]>pivot && start<end) {
				end--;
			}
			while(data[start]<=pivot && start<end) {
				start++;
			}
			int temp = data[start];
			data[start] = data[end];
			data[end] = temp;
		}
		int temp = data[end];
		data[end] = data[startIndex];
		data[startIndex] = temp;
		return end;
	}

  void sort(int arr[], int low, int high) 
  { 
      if (low < high) 
      { 	
          int pi = partion(arr, low, high); 
          // or int pi = partition(arr, low, high);	
          printArray(arr);
          sort(arr, low, pi-1); 
          sort(arr, pi+1, high); 
      } 
  } 

  static void printArray(int arr[]) 
  { 
      int n = arr.length; 
      for (int i=0; i<n; ++i) 
          System.out.print(arr[i]+" "); 
      System.out.println(); 
  } 
}

参考:

https://www.geeksforgeeks.org/quick-sort/

改进快排

快排对于一些极端的数据来说,可能会具有较低的性能,比如说顺序,倒序,很多数相等,等等。可以通过一些手段来提升这些情况的快排性能。

1)对于顺序,逆序来说,可以先对数据进行shuffle,打乱顺序之后在进行快排,下面仅仅写出shuffle函数:

//O(n)
private static void shuffleArray(int[] ar) {
   Random rnd = new Random();
   for (int i = ar.length - 1; i > 0; i--) {
       int index = rnd.nextInt(i + 1); // random between 0 and i
       exchange(ar, i, index);
   }
}

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

2)对于很多数据相等,比如说全是0,快排的时间复杂度非常高,相当于每次产生一个长度为n-1和1的子序列,因此时间复杂度是O(N^{2} ),因此,我们可以把每次和pivot相等的数据找出来,处理剩下的不相等的子序列,那么效率就很高了。该方法叫three-way-quicksort,详情请查看https://dzone.com/articles/algorithm-week-quicksort-three,下面是代码:

package Algorithm;

import java.util.Random;

public class ThreeWayQuickSort {

	public static void main(String[] args) {
		
		int dataSize = 1000000;
		int oneRate = 100;
		int[] arr = generateData(dataSize, oneRate);
        int n = arr.length; 
  
        ThreeWayQuickSort ob = new ThreeWayQuickSort(); 
        ob.sort(arr, 0, n-1); 
  
        System.out.println("sorted array:"); 
        printArray(arr); 
	}

	public void sort(int[] input, int lowIndex, int highIndex) {
	      
	      
	      if (highIndex<=lowIndex) return;
	      //lt - gt is same num as pivot
	      //lt is the left most index of pivot
	      int lt=lowIndex;
	      int gt=highIndex;
	      int i=lowIndex+1;
	      
	      int pivotIndex=lowIndex;
	      int pivotValue=input[pivotIndex];
	      
	      
	      while (i<=gt){
	          
	          
	          if (input[i]<pivotValue){
	              exchange(input, i++, lt++);
	          }
	          else if (pivotValue<input[i]){
	              exchange(input, i, gt--);
	          }
	          else{
	              i++;
	          }
	              
	          
	      }
	      
	      sort (input, lowIndex, lt-1);
	      sort (input, gt+1, highIndex);
	      
	      
	  }

	/*
	 * Input:
	 * 		dataSize
	 * 		oneRate: (oneRate/100)*dataSize is the number of 1
	 * 
	 * */
	public static int[] generateData(int dataSize, int oneRate) {
		int oneSize = dataSize * oneRate / 100;
		int[] result = new int[dataSize];
		Random random = new Random();
		for(int i=0;i<oneSize;i++) {
			result[i] = 1;
		}
		for(int j=oneSize;j<dataSize;j++) {
			result[j] = random.nextInt(Integer.MAX_VALUE);
		}
		return result;
	}
	
	public static void exchange(int[] input, int i, int j) {
		int temp = input[i];
		input[i] = input[j];
		input[j] = temp;
	}
	
	  static void printArray(int arr[]) 
	  { 
	      int n = arr.length; 
	      for (int i=0; i<n; ++i) 
	          System.out.println(arr[i]+" ");  
	  }
	  
}

2:堆排序

堆排序的详细细节请查看别的博客,这里我主要说一点,就是建堆的时间复杂度是O(n),虽然建堆过程需要对非叶子节点进行堆维护过程,看起来像是O(NlogN),但是,因为不是所有节点都需要维护堆,而且需要维护的节点树高比较低,因此实际的时间复杂度上界是O(n),证明如下(拍自算法导论):

package Algorithm;

public class HeapSort {

	public static void main(String[] args) {
 
	        int arr[] = {12, 11, 13, 5, 6, 7}; 
	  
	        HeapSort ob = new HeapSort(); 
	        ob.sort(arr); 
	  
	        System.out.println("Sorted array is"); 
	        printArray(arr); 

	}
	public void sort(int[] arr) {
		int length = arr.length;
		for(int i=length/2-1;i>=0;i--) {
			heapify(arr, length, i); 
		}
		 for (int i=length-1; i>=0; i--) 
	        { 
	            // Move current root to end 
	            int temp = arr[0]; 
	            arr[0] = arr[i]; 
	            arr[i] = temp; 
	  
	            // call max heapify on the reduced heap 
	            heapify(arr, i, 0); 
	        } 
	}
	void heapify(int arr[], int n, int i) {
		int largest = i; // Initialize largest as root 
        int l = 2*i + 1; // left = 2*i + 1 
        int r = 2*i + 2; // right = 2*i + 2 
  
        // If left child is larger than root 
        if (l < n && arr[l] > arr[largest]) 
            largest = l; 
  
        // If right child is larger than largest so far 
        if (r < n && arr[r] > arr[largest]) 
            largest = r; 
  
        // If largest is not root 
        if (largest != i) 
        { 
            int swap = arr[i]; 
            arr[i] = arr[largest]; 
            arr[largest] = swap; 
  
            // Recursively heapify the affected sub-tree 
            heapify(arr, n, largest); 
        } 
	}
	 static void printArray(int arr[]) 
	    { 
	        int n = arr.length; 
	        for (int i=0; i<n; ++i) 
	            System.out.print(arr[i]+" "); 
	        System.out.println(); 
	    } 
}

参考:

https://www.geeksforgeeks.org/heap-sort/

3:产生排列组合

使用迭代的方法,产生数据的组合以及排列。下面的代码中,permute代表产生排列,combination代表组合,排列的代码参考自"剑指offer",组合的代码是自己写的,如果有问题请指正。

package Algorithm;

import java.util.ArrayList;
import java.util.Collection;

public class Permutation {

	public static void main(String[] args) {
		int[] arr = new int[] {1,2,3};
		ArrayList<Integer> candidate = new ArrayList<>();
		for(int i =0;i<arr.length;i++) {
			candidate.add(arr[i]);
		}
		//排列
		ArrayList<Collection<Integer>> result = new ArrayList<>();
		ArrayList<Integer> now = new ArrayList<>();
		permute(result, candidate, now);
		System.out.println("total num:"+result.size());
		for(int i=0;i<result.size();i++) {
			System.out.println(result.get(i));
		}
		//组合
		ArrayList<Collection<Integer>> result2 = new ArrayList<>();
		ArrayList<Integer> now2 = new ArrayList<>();
		combination(result2, candidate, now2);
		System.out.println("total num:"+result2.size());
		for(int i=0;i<result2.size();i++) {
			System.out.println(result2.get(i));
		}
	}
	
	public static void permute(ArrayList<Collection<Integer>> result, ArrayList<Integer> candidate, ArrayList<Integer> now) {
		if(candidate.size()==1) {
			now.add(candidate.get(0));
			result.add(new ArrayList<>(now));
			now.remove(now.size()-1);
		} else {
			for(int i=0;i<candidate.size();i++) {
				now.add(candidate.get(i));
				ArrayList<Integer> temp = new ArrayList<>();
				for(int j=0;j<candidate.size();j++) {
					temp.add(candidate.get(j));
				}
				temp.remove(i);
				permute(result, temp, now);
				now.remove(now.size()-1);//不能remove(i)因为i总是从0开始,不代表添加的candidate.get(i)
			}
		}
	}
	
	public static void combination(ArrayList<Collection<Integer>> result2, ArrayList<Integer> candidate, ArrayList<Integer> now) {
		for(int i=1;i<=candidate.size();i++) {
			combinationMain(result2, candidate, now, 0, i);
		}
	}
	
	private static void combinationMain(ArrayList<Collection<Integer>> result2, ArrayList<Integer> candidate, ArrayList<Integer> now, int start, int length) {
		if(now.size()==length) {
			ArrayList<Integer> tempResult = new ArrayList<>(now);
			result2.add(tempResult);
		} else if(start<candidate.size()){
			now.add(candidate.get(start));
			combinationMain(result2, candidate, now, start+1, length);
			now.remove(now.size()-1);
			combinationMain(result2, candidate, now, start+1, length);
		}
	}
}

4:最近邻点对

最邻近点对算法使用的是分治的策略,详情请查看https://www.geeksforgeeks.org/closest-pair-of-points-using-divide-and-conquer-algorithm/,下面是我的java版本,比较重要的一点就是,该算法的时间复杂度是O(nlgn)如下:

其中需要说明的一点就是merge阶段,merge的时候需要从中间的区域找到最近的点对,看似需要n^2的时间复杂度,但是实际上,每个点周围需要比较的点实际上最多只有6个,因此时间复杂度实际上是O(n).

package Algorithm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

/*复杂度为O(nlogn)*/
public class ClosestPair {
	//比较器
	public static Comparator<node> compareX = new Comparator<node>() {

		@Override
		public int compare(node o1, node o2) {
			return o1.compareX(o1, o2);
		}
		
	};
	public static Comparator<node> compareY = new Comparator<node>() {

		@Override
		public int compare(node o1, node o2) {
			return o1.compareY(o1, o2);
		}
		
	};
	
	public static void main(String[] args) {
		node n1 = new node(2, 3);
		node n2 = new node(12, 30);
		node n3 = new node(40, 50);
		node n4 = new node(5, 1);
		node n5 = new node(12, 10);
		node n6 = new node(3, 4);
		node[] nodes = new node[] {n1, n2, n3, n4, n5, n6};
		double min = closest(nodes);
		System.out.println("closest distance is :"+min);
	}
	
	/*euclidean distance*/
	public static double dist(node o1, node o2) {
		return Math.sqrt(Math.pow(o1.x-o2.x, 2)+Math.pow(o1.y-o2.y, 2));
	}
	
	public static double closest(node[] nodes) {
		Arrays.sort(nodes, compareX);
		int end = nodes.length-1;
		int start = 0;
		return closestMain(nodes, start, end);
	}
	
	public static double closestStrip(ArrayList<node> strip, double min) {
		double result = min;
		Collections.sort(strip, compareY);//这一步的意义在于保证每个点周围只有最多6个点用于比较
		int size = strip.size();
		for(int i=0;i<size;i++) {
			for(int j=i+1;j<size;j++) {
				node temp1 = strip.get(i);
				node temp2 = strip.get(j);
				if(Math.abs(temp1.y-temp2.y)<min) {
					double distTemp = dist(temp1, temp2);
					if(distTemp<result) {
						result = distTemp;
					}
				}
			}
		}
		return result;
	}
	public static double closestMain(node[] nodes, int start, int end) {
		int length = end-start+1;
		if(length<=2) {
			return dist(nodes[start], nodes[end]);
		} else {
			int mid = start + (end-start)/2 ;
			double dl = closestMain(nodes, start, mid);
			double dr = closestMain(nodes, mid+1, end);
			double min = dl>dr?dl:dr;
			ArrayList<node> strip = new ArrayList<>();
			node midNode = nodes[mid];
			for(int i=start;i<=end;i++) {
				if(nodes[i].x-midNode.x-min<0.0000001) {
					strip.add(nodes[i]);
				}
			}
			return Math.min(min, closestStrip(strip, min));
		}
	}
}
class node{
	public int x;
	public int y;
	public node(int x, int y) {
		this.x = x;
		this.y = y;
	}
	public int compareX(node node1, node node2) {
		if(node1.x>=node1.x) {
			return 1;
		} else {
			return 0;
		}
	}
	
	public int compareY(node node1, node node2) {
		if(node1.y>=node1.y) {
			return 1;
		} else {
			return 0;
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值