面试题之常用–排序算法01

来源:互联网 发布:淘宝业绩查询 编辑:程序博客网 时间:2024/05/22 14:16
面试题之常用–排序算法01 

冒泡排序、快速排序、选择排序、插入排序、希尔排序、归并排序、堆排序


面试题之常用排序算法
2013-09-23      1 个评论       收藏     我要投稿
以下排序默认排序效果是从小到大,待排序序列:3,4,63,4,-9,0,1,32,-2
1.冒泡排序
     基本思想:依次交换相邻两个元素,使得大的数据往下沉(或小的数据往上附浮)
     第一步:比较相邻的两个元素,如果前者比后者大,则交换两元素。否则,不交换。
     第二步:重复第一步直到最后两个元素比较完成,此时,最大的元素已经在最后面了,此趟排序完成。
     第三步:去除最后元素,重复上述两步,对最后元素之前的数据进行排序。
     第四步:每趟排序完成后,大的数据会忘下沉,也就是需要排序的数据会越来越少,直到没有任何一对数据需要排序,排序成功
     
代码示例:[java] 
public void bullle(int[] array) {  
    for(int i=0; i<array.length-1; i++) {   //双层循环,第一层控制需要排序的趟数  
        for(int j=0; j<array.length-i-1; j++) {  //第二层,对第一个直到array.length-i-1的数据排序
if(array[j]>array[j+1]) {  //前者大于后者,交换元素
int temp = array[j];  
array[j] = array[j+1];  
array[j+1] = temp;  
}  
}  
}  
 }  
 
2.鸡尾酒排序(双向冒泡排序)
   基本思想:鸡尾酒排序法相当于双向冒泡排序,在鸡尾酒排序中,在从左向右的方向上,使大的数据往下沉,在回来也就是从右往左的方向上,使小的数据往上浮。
   过程示例:
 
   代码示例:[java] 
   public void cocktailSort(int[] array) {  
        int temp;  
        for(int k=0; k<array.length/2; k++) {  
            //使大的数据往下沉  
            for(int i=k; i<array.length-k-1; i++) {  
                if(array[i]>array[i+1]) {  
                    temp = array[i];  
                    array[i] = array[i+1];  
                    array[i+1] = temp;  
                }  
            }  
            //使小的数据往上浮  
            for(int j=array.length-2-k; j>k; j--) {  
                if(array[j]<array[j-1]) {  
                    temp = array[j];  
                    array[j] = array[j-1];  
                    array[j-1] = temp;  
                }  
            }  
        }  
    }  
    
3.快速排序
   基本思想:通过一趟排序,使得排序序列分为两部分,一部分的所有数据都小于另一部分,然后在按此方法分别对两部分进行排序,整个排序过程可用递归进行
   第一步:选取一个基准值(一般为待排序列的首元素),并记录,使得 i , j 分别指向首元素和最后一个元素
   第二步:从右侧开始,比较 j 指向元素和基准值的大小,如果大于j ,向前移动一个元素,直到找到一个比基准值小的元素,停止 j 的移动。
                 此时, 将 j 所指向的元素赋值给 i 所指向的元素,并让 i 向后移动 一 个元素。
   第三步:从左侧开始,比较 i 指向元素和基准值的大小,如果小于,i 向后移动一个元素,直到找到一个比基准值大的元素,停止 i 的移动。
                  此时, 将 i 所指向的元素赋值给 j 所指向的元素,并让 j 向前移动 一 个元素。
   第四步:重复上述步骤,知道 i 和 j 指向同一个元素,并将基准值赋值给i 和 j所指向的元素。
                 此时,序列已经被基准值分为两部分,左侧所有元素小于基准值,右侧所有的元素大于基准值
   第五步:使用相同方法,对基准值两侧的序列分别进行排序
   


   代码示例:[java]  
   
    int start = 0;
    int end = array.length-1;
private void quickSort(int[] array, int start, int end) { 
int i = start;
int j = end;  
int temp = array[start];  //记录基准值  
while(i<j) { 

//从右侧开始,找到第一个小于基准值的元素
//分两种情况:
      1.如果array[j]>temp,则j--,直到找到array[j]<temp
  2.否则,不执行,跳到下面if,把array[j]赋给array[i]
while(i<j && array[j]>temp) { 
    j--;  
}

//当array[j]<temp时
//将array[j]赋值给array[i]
if(i<j) {     //将j所在位置元素赋值给i所指向的元素,并使i向后移动
array[i] = array[j];  
i++;     //第一个元素已经比较出来了,从下一个元素开始
}    

//当i++后,array[i]<temp,i++
//否则,array[i]>temp, 跳到if ,执行array[j] = array[i]
//也就是把大于temp的值,复制到j那一位,以为第j位的值已经给了array[i],此时array[j]接受的是array[i++]
while(i<j && array[i]<temp) {  //从左侧开始,找到第一个大于基准值的元素
i++;  
}  
if(i<j) {                      //将i所在位置元素赋值给j所指向的元素,并使j向前移动
array[j] = array[i];  
j--;  
}  
}  
array[i] = temp;                  //当i>=j时,循环查找结束,将基准值赋值给i所在位置  
if(i-1>start) {                   //对基准值分割的两部分进行排序
quickSort(array, start, i-1);  
}  
if(i+1<end) {  
quickSort(array, i+1, end);  
}  
}  
 
4.插入排序
     基本思想:排序序列分为两部分,前一部分是已排序的序列,后一部分为待排序序列。
     第一步:从待排序序列中选择一个元素,依次比较其和已排序序列中的元素的大小,直到找到一个比它大的元素,并将其插入到这个元素前边
     第二步:重复上步,直到待排序序列中没有元素了。
     排序过程示例:


  
代码示例:
[java] 
public void InsertSort(int[] array) {  
        for(int i=1; i<array.length; i++) {  
            /* 
             * 第一种方法, 从前往后搜索,直到找到比它大的元素 
             * 然后将这个元素往后所有已排序序列中的元素向后移动 
             * */  
//          for(int j=0; j<i; j++) {  
//              if(array[i]<array[j]) {  
//                  int temp = array[i];  
//                  int k = i;  
//                  while(k>j) {  
//                      array[k] = array[k-1];  
//                      k--;  
//                  }  
//                  array[j] = temp;  
//                  break;  
//              }  
//          }  
            /* 
             * 第二种方法,从后往前搜索,直到找到一个比它小的元素 
             * 同样将这个元素往后所有已排序序列中的元素向后移动 
             * */  
            int temp = array[i];  
            int j = i;  
            if(array[j-1]>temp) {  
                while(j>=1 && array[j-1]>temp) {  
                    array[j] = array[j-1];  
                    j--;  
                }  
            }  
            //将这个元素赋值给相应位置  
            array[j] = temp;  
        }  
    }  
 
5.选择排序
    这个选择排序和插入排序看上去很像,都是从未排序的序列中选择一个元素,然后插入到已排序序列中。
不过它们有很大的不同,选择排序是从待排序序列中选择一个最小的元素,然后将这个元素插入到已排序序列的最后.
这个方法的优点是,不需要频繁的在数组中移动数据,只需要两两交换即可。
    基本思想: 
第一步:从待排序序列中通过比较,找到最小的元素,记录其位置和值
    第二步:将这个元素与待排序序列的第一个元素(也就是已排序序列最后元素的下一个元素)交换值。重复上述两步,直到所有元素已排完。
    
排序过程示例:
    代码示例:[java]   
public void selectSort(int[] array) {  
for(int i=0; i<array.length-1; i++) {  
int log = i;  
int min = array[i];  
//在待排序序列中找到最小的元素  
for(int j=i+1; j<array.length; j++) {  
if(min>array[j]) {  
min = array[j];  
log = j;  
}  
}  
if(log != i) {  
array[log] = array[i];  
array[i] = min;  
}  
   }
}  
 
6.归并排序
    基本思想:将待排序序列分为的两部分,然后对这两部分分别排序,最后将两部分按某种方法合并。
排序的过程是一个递归的过程。归并法是算法中分治法的一个典型代表。
因此,归并排序也分为两部分,分的过程和治(合并)的过程。
    合并使用的方法是:比较两个队列的队首元素,将小的从该队列中移除并插入到新队列的第一个位置,
然后在比较两队列的首元素大小,将小的从该队列移除并将其添加到新队列上次添加的元素的后面,
重复第二步知道有一个队列为空。将不为空的队列剩下所有的元素依次添加到新队列的最后。
    排序过程示例:
    第一次分割:{3,  4,  63,  2}{-9,  0,  1,  32,  -2}
    下面以分割后的第一部分为例。
    第二次分割:{{3,4}{63, 2}} 第三次分割:{{{3}{4}}{{63}{2}}}
    合并的过程:第一次合并:{{3,4}{2,63}} 第二次合并:{2,3,4,63}至此,第一部分已经排序完毕,第二部分排序方法相同,为{-9,-2,0,1,32}
    最后将两部分合并为:{-9,-2,0,1,2,3,4,32,63}
  
    代码示例:[java]  
private void MergeSort(int[] array, int start, int end) {  
        if(start == end) {  
            return;  
        }  
        int middle = (end - start + 1)/2 + start;  
        //分的过程  
        MergeSort(array, start, middle-1);  
        MergeSort(array, middle, end);  
        //合并的过程  
        merge(array, start, middle, end);  
    }  
      
    private void merge(int[] array, int start, int middle, int end) {  
        int i = start, j = middle, log = 0;  
        int[] copy = new int[end-start+1];  
        while(i<middle && j<=end) {  
            if(array[i]>array[j]) {  
                copy[log] = array[j];  
                j++;  
            } else {  
                copy[log] = array[i];  
                i++;  
            }  
            log++;  
        }  
        while(i<middle) {  
            copy[log++] = array[i++];  
        }  
        while(j<=end) {  
            copy[log++] = array[j++];  
        }  
        for(int k=0; k<copy.length; k++) {  
            array[start++] = copy[k];  
        }  
        copy = null;  
    }  



堆排序算法
1、基本思想:
  堆排序是一种树形选择排序,是对直接选择排序的有效改进。
  堆的定义下:具有n个元素的序列 (h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)
                或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。
由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。
完全二叉树可以很直观地表示堆的结构。
堆顶为根,其它为左子树、右子树。
  思想:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个 堆,
         这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。
然后对前面(n-1)个数重新调整使之成为堆。
依此类推,直到只有两个节点的堆,并对 它们作交换,最后得到有n个节点的有序序列。
从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。
所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。


2.算法实现:
public class HeapSort {
public static void main(String[] args) {
int[] a={49,38,65,97,76,13,27,49,78,34,12,64};
int arrayLength=a.length;  
//循环建堆  
for(int i=0;i<arrayLength-1;i++){  
//建堆  
buildMaxHeap(a,arrayLength-1-i);  
//交换堆顶和最后一个元素  
swap(a,0,arrayLength-1-i);  
System.out.println(Arrays.toString(a));  
}  
}
//对data数组从0到lastIndex建大顶堆
public static void buildMaxHeap(int[] data, int lastIndex){
//从lastIndex处节点(最后一个节点)的父节点开始 
for(int i=(lastIndex-1)/2;i>=0;i--){
//k保存正在判断的节点 
int k=i;
//如果当前k节点的子节点存在  
while(k*2+1<=lastIndex){
//k节点的左子节点的索引 
int biggerIndex=2*k+1;
//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
if(biggerIndex<lastIndex){  
//若果右子节点的值较大  
if(data[biggerIndex]<data[biggerIndex+1]){  
//biggerIndex总是记录较大子节点的索引  
biggerIndex++;  
}  
}  
//如果k节点的值小于其较大的子节点的值  
if(data[k]<data[biggerIndex]){  
//交换他们  
swap(data,k,biggerIndex);  
//将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值  
k=biggerIndex;  
}else{  
break;  
}  
}
}
}
//交换
private static void swap(int[] data, int i, int j) {  
int tmp=data[i];  
data[i]=data[j];  
data[j]=tmp;  

}




希尔算法
1、基本思想:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,
再对全体记录进行依次直接插入排序。
2、操作方法:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
希尔排序的示例:


 3、算法实现:
/**希尔排序的原理:根据需求,如果你想要结果从大到小排列,它会首先将数组进行分组,然后将较大值移到前面,较小值
 * 移到后面,最后将整个数组进行插入排序,这样比起一开始就用插入排序减少了数据交换和移动的次数,可以说希尔排序是加强
 * 版的插入排序
 * 拿数组5, 2, 8, 9, 1, 3,4来说,数组长度为7,当increment为3时,数组分为两个序列
 * 5,2,8和9,1,3,4,第一次排序,9和5比较,1和2比较,3和8比较,4和比其下标值小increment的数组值相比较
 * 此例子是按照从大到小排列,所以大的会排在前面,第一次排序后数组为9, 2, 8, 5, 1, 3,4
 * 第一次后increment的值变为3/2=1,此时对数组进行插入排序,
 *实现数组从大到小排
 */
    public static void shellSort(int[] data) 
    {
        int j = 0;
        int temp = 0;
        //每次将步长缩短为原来的一半
        for (int increment = data.length / 2; increment > 0; increment /= 2)
        {
for (int i = increment; i < data.length; i++) 
{
temp = data[i];
for (j = i; j >= increment; j -= increment) 
{
if(temp > data[j - increment])//如想从小到大排只需修改这里
{   
data[j] = data[j - increment];
}
else
{
break;
}


data[j] = temp;
}
    
        }
    }
 4、效率
 时间复杂度:O(n^2). 





0 1
原创粉丝点击