Chp11: Sorting and Searching

时间:2021-09-04 19:23:51

Common Sorting Algo:

Chp11: Sorting and Searching

Bubble Sort: Runime: O(n2) average and worst case. Memory: O(1).

 void BubbleSortArray(){
for(int i=1;i<n;i++)
for(int j=0;i<n-i;j++)
if(a[j]>a[j+1]){//比较交换相邻元素
int temp;
temp=a[j]; a[j]=a[j+1]; a[j+1]=temp;
}
}

Selection Sort: Runtime: O(n2) average and worst case. Memory: O(1).

 void SelectSortArray(){
int min_index;
for(int i=0;i<n-1;i++){
min_index=i;
for(int j=i+1;j<n;j++)//每次扫描选择最小项
if(arr[j]<arr[min_index]) min_index=j;
if(min_index!=i){//找到最小项交换,即将这一项移到列表中的正确位置
int temp;
temp=arr[i]; arr[i]=arr[min_index]; arr[min_index]=temp;
}
}
}

Merge Sort: Runtime: O(nlogn) average and worst case. Memory: Depends.

 void mergeSort(int[] array, int low,int high){
if(low < high){
int middle = (low + high) / 2;
mergeSort(array, low, middle);
mergeSort(array, middle + 1, high);
merge(array, low, middle, high);
}
}
void merge(int[] array, int low, int middle, int high){
int[] helper = new int[array.length];
for(int i = low; i <= high; i ++)
helper[i] = array[i];
int helperLeft = low;
int helperRight = middle + 1;
int current = low;
while(helperLeft <= middle && helperRight <= high){
if(helper[helperLeft] <= helper[helperRight])
array[current ++] = helper[helperLeft ++];
else
array[current ++] = helper[helperRight ++];
}
int remaining = middle - helperLeft;
for(int i = 0; i <= remaining; i ++)
array[current + i] = helper[helperLeft + i];
}

Quick Sort: Runtime: O(nlogn) average, O(n2) worse case. Memory: O(nlogn).

In quick sort, we pick a random element and partition the array, such that all numbers that are less than the partitioning element come before all elements that are greater than it. The partitioning can be performed efficiently through a series of swaps.

 void quickSort(int[] arr, int left, int right){
int index = partition(arr, left, right);
if(left < index - 1) quickSort(arr, left, index - 1);
if(right < index - 1) quickSort(arr, index, right);
}
int partition(int[] arr, int left, int right){
int pivot = arr[(left + right) / 2];
while(left <= right){
while(arr[left] < pivot) left ++;
while(arr[right] > pivot) right --;
if(left <= right){
swap(arr, left, right);
left ++;
right --;
}
}
return left;
}
 //pro4 : quick sort
public int adjust_array(int[] input,int low,int high){
//set the pivot to be the first element of the array
int pivot = input[low];
int exchange = 0;
while(low < high){
//from the tail to header,find the item smaller than the pivot
while(input[high] >= pivot && high > low)
high --;
exchange = input[high];
input[high] = input[low];
input[low] = exchange;
//from the header to tail,find the item larger than the pivot
while(input[low] <= pivot && high > low)
low ++;
exchange = input[high];
input[high] = input[low];
input[low] = exchange;
}
//set the mid to be pivot
input[low] = pivot; return low;
} public void quick_sort(int[] input, int low, int high){
//set the low and high pointer
if(low < high){
int mid = adjust_array(input,low,high);
quick_sort(input, 0, mid - 1);
quick_sort(input, mid + 1, high);
}
} public void pro4(){
int[] input = {20,1,2,40,7,90,11};
int low = 0;
int high = input.length - 1;
quick_sort(input,low,high);
for(int i = 0; i < input.length; i ++){
System.out.println(input[i]);
}
}

Radix Sort: Runtime: O(kn).

It is a sorting algo for intergers that takes advantage of the fact that integers have a finite number of bits.

 Void RadixSort(Node L[],length,maxradix)
{
Int m,n,k,lsp;
k=1;m=1;
Int temp[10][length-1];
Empty(temp); //清空临时空间
While(k<maxradix) //遍历所有关键字
{
For(int i=0;i<length;i++) //分配过程
{
If(L[i]<m)
Temp[0][n]=L[i];
Else
Lsp=(L[i]/m)%10; //确定关键字
Temp[lsp][n]=L[i];
n++;
}
CollectElement(L,Temp); //收集
n=0;
m=m*10;
k++;
}
}

Bucket sort:

Bucket sort, or bin sort, is a sorting algorithm that works by partitioning an array into a number of buckets. Each bucket is then sorted individually, either using a different sorting algorithm, or by recursively applying the bucket sorting algorithm. It is a distribution sort, and is a cousin of radix sort in the most to least significant digit flavour. Bucket sort is a generalization of pigeonhole sort. Since bucket sort is not a comparison sort, the Ω(n log n) lower bound is inapplicable. The computational complexity estimates involve the number of buckets.

Bucket sort works as follows:

  1. Set up an array of initially empty "buckets."
  2. Scatter: Go over the original array, putting each object in its bucket.
  3. Sort each non-empty bucket.
  4. Gather: Visit the buckets in order and put all elements back into the original array.

Pseudocode

function bucketSort(array, n) is
buckets ← new array of n empty lists
for i = 0 to (length(array)-1) do
insert array[i] into buckets[msbits(array[i], k)]
for i = 0 to n - 1 do
nextSort(buckets[i])
return the concatenation of buckets[0], ...., buckets[n-1]

Here array is the array to be sorted and n is the number of buckets to use. The function msbits(x,k) returns the k most significant bits of x (floor(x/2^(size(x)-k))); different functions can be used to translate the range of elements in array to n buckets, such as translating the letters A–Z to 0–25 or returning the first character (0–255) for sorting strings. The function nextSort is a sorting function; using bucketSort itself as nextSort produces a relative of radix sort; in particular, the case n = 2 corresponds toquicksort (although potentially with poor pivot choices).

Heap Sort: Runtime:O(nlgn) average and worse case.

 //堆排序
template
void Sort::HeapSort(T* array, int size)
{
int lastP = size / ;
//从最后一个有孩子的结点开始建初始堆
for(int i = lastP - ; i >= ; i--)
{
HeapAjust(array, i, size);
}
int j = size;
//将堆顶元素和无序区间的最后一个元素交换,再调整堆
while(j > )
{
Swap(array, , j - );
j--;
HeapAjust(array, , j);
}
}
//调整堆
template
void Sort::HeapAjust(T *array, int toAjust, int size)
{
int pos = toAjust;
while((pos * + ) < size)
{
int lChild = pos * + ;
if(array[lChild] > array[pos])
{
pos = lChild;//左孩子大
}
int rChild = lChild + ;
if(rChild < size && array[rChild] > array[pos])
{
pos = rChild;//右孩子更大
}
if(pos != toAjust) //父结点比其中一个孩子小
{
Swap(array, toAjust, pos);
toAjust = pos;
}
else
{
break;
}
}
}

Insertion Sort: Runtime O(n2).

 //插入排序
template
void Sort::InsertSort(T* array, int size)
{
for(int i = ; i < size; i++)
{
for(int j = i; j > ; j--)
{
if(array[j] < array[j - ])
{
Swap(array, j, j-);
}
}
}
}

Shell Sort: Runtim: O(nlog2^n) ~ O(n^1.5).

 void ShellSortArray()
{
for(int incr=3;incr<0;incr--)//增量递减,以增量3,2,1为例
{
for(int L=0;L<(n-1)/incr;L++)//重复分成的每个子列表
{
for(int i=L+incr;i<n;i+=incr)//对每个子列表应用插入排序
{
int temp=arr[i];
int j=i-incr;
while(j>=0&&arr[j]>temp)
{
arr[j+incr]=arr[j];
j-=incr;
}
arr[j+incr]=temp;
}
}
}
}

总结:

1.O(n^2)性能分析

平均性能为O(n^2)的有:直接插入排序,选择排序,冒泡排序

在数据规模较小时(9W内),直接插入排序,选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。

2.O(nlogn)性能分析

平均性能为O(nlogn)的有:快速排序,归并排序,希尔排序,堆排序。其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。

这四种排序可看作为“先进算法”,其中,快排效率最高,但在待排序列基本有序的情况下,会变成冒泡排序,接近O(n^2).

希尔排序对增量的标准没有较为满意的答案,增量对性能会有影响。

归并排序效率非常不错,在数据规模较大的情况下,比希尔排序和堆排序要好。

多数先进的算法都是因为跳跃式的比较,降低了比较次数,但牺牲了排序的稳定性。

3. 插入排序,冒泡排序,二叉树排序,归并排序都是稳定的

选择排序,希尔排序,快速排序,堆排序是不稳定的。

四、排序算法选择

1.数据规模较小

(1)待排序列基本序的情况下,可以选择直接插入排序

(2)对稳定性不作要求宜用选择排序,对稳定性有要求宜用插入或冒泡

2.数据规模不是很大

(1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。

(2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序

3.海量级别的数据,必须按块放在外存上

(1)对稳定性有求,则可考虑归并排序。

(2)对稳定性没要求,宜用堆排序

4.序列初始基本有序(正序),宜用直接插入,冒泡,随机快排

外部排序

外部排序指的是大文件的排序,面试的时候,面试官喜欢问,给你一个非常非常大的文件(比如1T),一行一个数(或者一个单词),内存最多只有8G,硬盘足够大,CPU很高级……然后要你给这个文件里面的数据排序。你要怎么办?

这其实就要用到外部排序。就是说要借助外存储器进行多次的内/外存数据的交换,因为内存不足以加载所有的数据,所以只能一部分一部分地加载。

所以外部排序的思想就是:分两个独立的阶段。

首先,可按内存的大小,将外存上含n个记录的文件分成若干长度为 x 的子文件或段,依次读入内存,并利用有效的内部排序方法对它们进行排序,并将排序后得到的有序子文件 重新写入外存,通常称这些有序的子文件为归并段或顺串。然后,对这些归并段进行逐趟归并,使归并段逐渐由小到大,直至得到整个有序文件为止。

因此现在的问题就转化为如何归并两个大文件。这个读者朋友们想一下就明白了。就是把这两个文件按内存的大小,一部分一部分从小到大加载出来并,再写回外存。

Questions:

11.1 Given two sorted arrays, A and B, where A has a large enough buffer at the end to hold B.

Merge a and b, starting from the last element in each!

11.2 Write a method to sort an array of strings so that all the anagrams are next to each other.

Implement comparator for this problem:

 public class AnagramComparator implements Comparator<String>{
public String sortChars(String s){
char[] content = s.toCharArray();
Arrays.sort(content);
return new String(content);
}
public int compare(String s1, String s2){
return sortChars(s1).compareTo(sortChars(s2));
}
}
 public void sort(String[] array){
Hashtable<String, LinkedList<String>> hash = new Hashtable<String, LinkedList<String>>();
for(String s : array){
String key = sortChars(s);
if(!hash.containsKey(key)) hash.put(key, new LinkedList<String>());
LinkedList<String> anagrams = hash.get(key);
anagrams.push(s);
}
int index = 0;
for(String key : hash.keySet()){
LinkedList<String> list = hash.get(key);
for(String t : list){
array[index] = t;
index ++;
}
}
}

The algorithm above is a modification of bucket sort.

11.3 Given a sorted array of n integers that has been rotated an unknown number of times, write code to find an element in the array. the array is sorted in increasing order at begining.

If we look a bit deeper, we can see that one half of the array must be ordered normally(in increasing order).We can therefore look at the normally ordered half to determine whether we should search the left or right half.

 public int search(int[] array, int left, int right, int x){
int mid = (left + right) / 2;
if(x == array[mid]) return mid;
if(left < right) return -1;
//either the left or right half must be normally ordered. find out which side is normally ordered, and then use the normally ordered half to figure out which side to search to find x.
if(array[left] < array[mid]){//left half normally ordered
if(x >= array[left] && x <= array[mid]) return search(array, left, mid - 1, x);
else return search(array, mid + 1, right, x);
}else if(array[mid] < array[left]){//right is normally ordered
if(x >= array[mid] && x <= array[right]) return search(array, mid + 1, right, x);
else return search(array, left, mid - 1, x);
}else if(array[left] == array[mid]){// left half is all repeats
if(array[mid] != array[right]) return search(array, mid + 1, right, x);
else{// else we have to search both halves
int result = search(array, left, mid - 1, x);
if(result == -1) return search(array, mid + 1, right, x);
else return result;
}
}
return -1;
}

11.4 Imagine you hace a 20GB file with one string per line. Explain how you would sort the file.

When an interviewer gives a size limit, it should tell you that they don't want you to bring all the data into memory.

So we should only bring part of the data into memory.

We'll divide the file into chunks which are xMB each, where x is the amout of memory we have available. Each chunk is sorted separately and then saved back to the file system.

Once all the chunks are sorted, we then merge the chunks, one by one. At the end, we have a fully sorted file.

 11.8 Imagine you are reading in a stream of integers. Periodically, you wish to be able to look up the rank of a number x(the number of values less than or equal to x). Implement the data structures and algorithms to support these operations.

 public class Question{
private static RankNode root = null;
public static void track(int number){
if(root == null) root = new RankNode(number);
else root.insert(number);
}
public static int getRankOfNumber(int number){
return root.getRank(number);
}
...
}
public class RankNode{
public int left_size = 0;
public RankNode left, right;
public int data = 0;
public RankNode(int d){
data = d;
}
public void insert(int d){
if(d <= data){
if(left != null) left.insert(d);
else left = new RankNode(d);
left_size ++;
}else{
if(right != null) right.insert(d);
else right = new RankNode(d);
}
}
public int getRank(int d){
if(d == data) return left_size;
else if(d < data){
if(left == null) return -1;
else return left.getRank(d);
}else{
int right_rank = right == null ? -1 : right.getRank(d);
if(right_rank == -1) return -1;
else return left_size + 1 + right_rank;
}
}
}