leetcode排序算法基础--2017-8-1

来源:互联网 发布:淘宝上怎么删除评价 编辑:程序博客网 时间:2024/06/03 22:51


1.插入排序

1.1 直接插入排序:

插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。经典的插入排序算法有直接插入排序和希尔排序。 
直接插入排序的基本思想是:将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。 
图示如下(自百度图片,若有侵权,请告知): 


Java代码实现:

package Test;

public class ZhiJieChaRuPaiXu {
public static void insertSort(int [] numbers){ //直接插入排序函数
//计算第i个元素的插入位置(i之前的元素都已经排好序了)
for (int i=1;i<numbers.length;i++){
int number=numbers[i];  //赋给临时变量
int j=i-1;     //该变量用于标志插入位置
for (;j>=0;j--){     //相当于是找到那个j,相当于是插入位置
if (numbers[j]<=number) break;
}
//找到位置之后,将第j+1位置的元素到第i-1位置的元素依次往后移
for (int k=i;k>j+1;k--)
numbers[k]=numbers[k-1];
//然后将原来第i个元素插入到第j+1个位置
numbers[j+1]=number;
}
}


public static void main(String[] args) {
// TODO Auto-generated method stub
 int a[]={53,27,36,15,69,42};
 //System.out.print(a.length);
 insertSort(a);
 for (int i=0;i<a.length;i++){
 System.out.print(a[i]+" ");
 }
}
}
答案:



1.2 希尔排序
希尔排序是对直接插入排序的改进,其实质就是分组插入排序,该方法又称缩小增量排序。
算法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的一系列元素组成)分别进行直接插入排序,然后依次缩减增量再进行排序,直到增量为1(即对全体数据元素进行一次直接插入排序)。 
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。

在实现希尔排序的时候,我们先看一眼直接插入排序的实现,然后在看希尔排序的实现,我们会发现,其实希尔排序就比直接插入排序多了一个基于步长的外层循环。 
可以看到,希尔排序就增加了一个外层的基于步长的循环,然后内部是直接插入排序,只不过循环的增量不是1,而是外层的那个gap步长。
C++代码实现:
#include <iostream>
using namespace std;

//直接插入排序,受dk(间隔)影响
void shellInsertSort(int r[], int n, int dk){   //dk为增量
int i;
for (i = dk; i < n; i++){  //从索引为DK的开始,这一步很关键
if (r[i] < r[i - dk]){ //若第i个元素大于i-dk(dk是增量,直接插入排序的增量为1),这一步也关键
int j = i - dk;  
int x = r[i];  //赋值为哨兵,既存储待排序元素
while (x < r[j] && j >= 0){ //查找在有序表中的位置
r[j + dk] = r[j];   //元素后移
j = j - dk; //比较的指针往前走
}
r[j + dk] = x;  //插入到正确位置,这里的j+dk,注意上面两条语句,每遇到一个符合条件的
//一个已经往后走了dk, 另外一个也向前走了dk,所以当中的这个插入位置就是j+dk
}
}
}


//shell排序
void shellSort(int r[], int n){
int dk;
dk = n / 2;
while (dk >= 1){
shellInsertSort(r, n, dk);  //函数再调用函数,这样思路比较清楚
dk = dk / 2;
}
}


//打印函数
void print(int r[], int n){
int i;
for (i = 0; i < n; i++){
cout << r[i] << " ";
}
cout << "\n";
}

int main(){
int a[8] = { 3, 1, 5, 5, 2, 4, 9, 6 };
cout << "排序前:\n";
print(a, 8);
cout << "排序后:\n";
//shellInsertSort(a, 8, 1);   //直接插入排序
shellSort(a, 8);    //希尔插入排序
print(a, 8);
return 0;
}



2.交换排序

2.1 冒泡排序
交换类排序的思想是通过一系列交换逆序元素进行排序的方法,经典的交换排序算法有冒泡排序和快速排序。 
冒泡排序应该算是最简单的排序算法了,其过程如下: 
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 
2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 
3. 针对所有的元素重复以上的步骤,除了最后一个。 
4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
Java代码实现:
package Test;
import java.util.Arrays;

public class MaoPaoPaiXu {
public static void swap(int[] numbers, int i,int j){
int temp;
if(numbers[i]==numbers[j])return;
temp=numbers[i];
       numbers[i]=numbers[j];
   numbers[j]=temp;
}

public static void bubbleSort(int[] numbers){
for (int i=1;i<numbers.length;i++){   //这里的i指的是比较次数,一共N-1次
for (int j=0;j<numbers.length-i;j++){   //经过一轮,则比较的个数减去1,加入i=1,因为还有j+1
if (numbers[j]>numbers[j+1])  //后面的大
swap(numbers,j,j+1);
}
}
}


public static void main(String[] args) {
// TODO Auto-generated method stub
int[] numbers={5,1,6,7,0,4,2,3};
bubbleSort(numbers);
//System.out.println(Arrays.toString(numbers));
for (int i=0;i<=numbers.length-1;i++){
System.out.print(numbers[i]+" ");
}
}

}


2.2 快速排序

快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小的程序方面的考试如软考,考研中也常常出现快速排序的身影。

总的说来,要直接默写出快速排序还是有一定难度的,因为本人就自己的理解对快速排序作了下白话解释,希望对大家理解有帮助,达到快速排序,快速搞定

 

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

该方法的基本思想是:

1.先从数列中取出一个数作为基准数。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。

 

虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法

先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。

 

以一个数组作为示例,取区间第一个数为基准数。

0

1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85

初始时,i = 0;  j = 9;   X = a[i] = 72

由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。

从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;

 

数组变为:

0

1

2

3

4

5

6

7

8

9

48

6

57

88

60

42

83

73

88

85

 i = 3;   j = 7;   X=72

再重复上面的步骤,先从后向前找,再从前向后找

从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;

从i开始向后找,当i=5时,由于i==j退出。

此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。

 

数组变为:

0

1

2

3

4

5

6

7

8

9

48

6

57

42

60

72

83

73

88

85

可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。

 

 对挖坑填数进行总结

1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

照着这个总结很容易实现挖坑填数的代码:


参考网址:http://blog.csdn.net/morewindows/article/details/6684558
C++代码实现:
#include <iostream>
using namespace std;

int AdjustArray(int s[], int l, int r){    //返回调整后基准数的位置
int i = l, j = r;
int x = s[l];  //s[l]就是第一个坑
while (i < j){
//从右向左找小于x的数来填s[i]
while (i < j && s[j] >= x)
j--;
if (i < j){
s[i] = s[j];  //将s[j]填到s[i]中,s[j]就形成一个新的坑
i++;
}
//从左向右找大于或等于x的数来填s[j]
while (i < j && s[i] < x)
i++;
if (i < j){
s[j] = s[i];
j--;
}
}
//退出的时候,i等于j,将x填到这个坑中
s[i] = x;
return i;
}

//在写分治法的代码,记住全部都是一分为2,分而治之
void quick_sort(int s[], int l, int r){
if (l < r){   //假如l=r,就只有一个元素了,就不需要调用了,也就是递归结束了
int i = AdjustArray(s, l, r);  //先用挖坑法调整s[]
quick_sort(s, l, i - 1);    //递推调用
quick_sort(s, i + 1, r);
}
}


//打印函数
void print(int r[], int n){
int i;
for (i = 0; i < n; i++){
cout << r[i] << " ";
}
cout << "\n";
}

int main(){
int a[8] = { 3, 1, 7, 5, 2, 4, 9, 6 };
cout << "排序前:\n";
print(a, 8);
cout << "排序后:\n";
quick_sort(a, 0,7);    //快速排序
print(a, 8);
return 0;
}

参考网址2:http://blog.csdn.net/lemon_tree12138/article/details/50622744


3.选择排序
3.1 直接选择排序

选择排序是一种简单直观的排序算法。其基本思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。经典的选择排序算法有:直接选择排序和堆排序。

下面是直接选择排序的Java实现:

package leetcodeSort;

public class test {
    //交换数组中的两个元素
    public static void swap(int[] numbers,int i, int j){
        int temp= numbers[i];
        numbers[i]=numbers[j];
        numbers[j]=temp;
    }
    
    public static void selectSort(int[] numbers){
        for (int i=0;i<numbers.length;i++){
            int min=i; //该轮比较中的最小值的位置
            for (int j=i+1;j<numbers.length;j++){
                if (numbers[j]<numbers[min])
                    min=j;
                swap(numbers,i,min);
            }
        }
    }

    
    public static void print(int[] numbers){
        for (int i=0;i<numbers.length;i++){
         System.out.printf(numbers[i]+" ");
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
       int[] a={6,4,5,3,7,2,1};
       selectSort(a);
       print(a);
    }

}

结果输出:

1 2 3 4 5 6 7


3.2 堆排序

参考网址:http://blog.csdn.net/theonegis/article/details/71698405

堆排序

堆排序时如何进行的呢(以大顶堆为例)?
1. 对数据构建大顶堆。这样最大的元素位于堆顶,即数组的第一个元素。
2. 交换数组第一个元素和最后一个元素。
3. 对第一个元素到除倒数第一个元素之外的数据序列再构建大顶堆。其实这就是重复第一步了。
4. 然后再重复第二步,交换第一个元素和倒数第二个元素。
5. 以此类推,直到堆中只有一个元素。

这个过程中,每次取出最大的元素,然后对剩下的元素再进行建堆。
下图给出了构建大顶堆的过程(图片来自网络,侵权删)


注意:为什么在initHeap()函数最外层的循环是从(maxIndex - 1) / 2开始的?
其实,你从maxIndex开始也没错,相等于我们从最后一层最后一个元素开始进行比较。这样只不过是做了无用功而已。
initHeap()函数是用来构建大顶堆的我们需要从倒数第二层节点开始依次进行比较。而我们需要计算倒数第二层元素在数组中的索引位置。
比如我们的堆有 k 层,则对于满二叉树来说,总共有 2k1个结点,而第k 层有 2k1 个结点,则前面 k1 层有 2k12k1=2k1 个结点。就是说前面 k1 层和第 k 层有相同的结点。如果我们总共有 n 个数据(或者说结点),那么我们应该从 n21 开始比较(为什么减一,因为我们数组下标是从0开始的)。initHeap()传进来的是最大下标值,则我们应该从maxInde+121=maxInde12 开始进行比较。
这是对于满二叉树的数学推到,我们的是完全二叉树,相同深度的完全二叉树的结点肯定小于等于满二叉树。就是说最后层次结点数量肯定会比总数的一半少,我们从(maxIndex-1)/2开始进行比较肯定是没问题的。
下面是堆排序的Java实现:

package leetcodeSort;

public class java3 {
    /**
        * 调整数据元素,构建大顶堆(构建从0索引位置到maxIndex索引位置的大顶堆)
        * @param numbers  需要排序的数据元素
        * @param maxIndex  当前的最大位置指针
        */
    public static void initHeap(int[] numbers, int maxIndex){
        //请注意这里为什么需要进行(maxIndex-1)/2次循环
        for (int i=(maxIndex-1)/2;i>=0;i--){
            int parent=i;//记录当前节点(父节点)
            //如果当前节点存在子节点,则进行比较
            while(parent*2+1<=maxIndex){
                int bigger=parent*2+1; //记录较大元素节点,初始为左
                int right=parent*2+2;
                if(right<=maxIndex&&numbers[bigger]<numbers[right])
                    bigger=right;
                //如果父节点小于子节点,则进行交换
                if (numbers[parent]<numbers[bigger]){
                    swap(numbers,parent,bigger);
                    parent=bigger;
                    //因为parent位置的元素发生了改变,所以
                    //需要判断以parent为根的左右元素是否满足大顶堆的条件
                }else break;        
            }
        }
    }
    
    public static void swap(int[] numbers, int i, int j){
        int temp=numbers[i];
        numbers[i]=numbers[j];
        numbers[j]=temp;
    }
    
    public static void heapSort(int[] numbers){
        for (int i=0;i<numbers.length;i++){
            initHeap(numbers,numbers.length-1-i); //构建大顶堆
            swap(numbers,0,numbers.length-1-i); //交换最后位置的元素和堆顶元素
        }
    }


    public static void main(String[] args) {
        // TODO Auto-generated method stub
         int[] a={4,1,3,2,16,9,10,14,8,7};
         heapSort(a);
         for (int i=0;i<a.length;i++){
             System.out.print(a[i]+" ");
         }
    }

}

答案:1 2 3 4 7 8 9 10 14 16



4.归并排序

 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,归并排序将两个已排序的表合并成一个表。

归并排序基本原理

通过对若干个有序结点序列的归并来实现排序。
所谓归并是指将若干个已排好序的部分合并成一个有序的部分
举例说明"归并排序的排序过程"

归并排序基本思想

设两个有序的子序列(相当于输入序列)放在同一序列中相邻的位置上:array[low..m],array[m + 1..high],先将它们合并到一个局部的暂存序列 temp (相当于输出序列)中,待合并完成后将 temp 复制回 array[low..high]中,从而完成排序。

在具体的合并过程中,设置 i,j 和 p 三个指针,其初值分别指向这三个记录区的起始位置。合并时依次比较 array[i] 和 array[j] 的关键字,取关键字较小(或较大)的记录复制到 temp[p] 中,然后将被复制记录的指针 i 或 j 加 1,以及指向复制位置的指针 p加 1。重复这一过程直至两个输入的子序列有一个已全部复制完毕(不妨称其为空),此时将另一非空的子序列中剩余记录依次复制到 array 中即可。若将两个有序表合并成一个有序表,称为2-路归并


举例说明"归并排序的排序过程"
看下面归并排序的两种排序过程

 1.待排序列(14,12,15,13,11,16)

假设我们有一个没有排好序的序列,那么首先我们使用分割的办法将这个序列分割成一个个已经排好序的子序列。然后再利用归并的方法将一个个的子序列合并成排序好的序列。分割和归并的过程可以看下面的图例。
这个图比较好地诠释了算法:

 先"分割"再"合并"
从上图可以看出,我们首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的数据,再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。
2.待排序列(25,57,48,37,12,92,86)

源码实现(Java版):
package Test;

import java.util.Arrays;

public class GuiBingPaiXu {
//合并
public static void merge(int[] numbers, int start,int mid,int end){
int i=start;
int j=mid+1;
int k=0;
int[] sortedArr=new int[end-start+1]; //临时数组,用于存放排好序的
//下标从start到end的
//从start和mid+1开始,同时扫描进行排序
//小的在前面
while (i<=mid && j<=end){
if(numbers[i]<numbers[j])sortedArr[k++]=numbers[i++];
else sortedArr[k++]=numbers[j++];
}
//当i(或j)指针之一指向末尾,另一个指向的数据直接添加到排好序的末尾即可
while (i<=mid)sortedArr[k++]=numbers[i++];
while (j<=end) sortedArr[k++]=numbers[j++];

for (i=0;i<k;i++)
numbers[start+i]=sortedArr[i];
}


public static void mergeSort(int[] numbers, int start, int end){
if (start<end){  //截止条件是只有一个元素,start=end
int mid=start+(end-start)/2;
mergeSort(numbers,start,mid); //递归,这是精髓
mergeSort(numbers,mid+1,end);//递归,这是精髓,打个比方就是进门拿东西,有多扇门,门傍边都有东西,最里面有个东西,先拿最里面的东西,然后把每个门旁的东西也要带出来的
//左右两边排好序后以后再进行合并
merge(numbers,start,mid,end);
}
}


public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a={3,1,2,5,4,6,9,7,10,8};
mergeSort(a,0,a.length-1);
System.out.println(Arrays.toString(a));
}
}
结果:

5.基数排序

基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。比较官方地说,基数排序是一种基于多关键字的排序。

基数排序具体过程如下: 
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。 
然后,从最低位开始,依次进行一次排序。这个排序并非比较大小,而是将对应的数字放置在其对应的桶中。即个位数字是0的数字放置在索引为0的桶,以此类推。这个过程称为“分配过程”。 
个位完成这个操作以后,然后依次从桶中取出数字形成新的序列。这个过程称为“收集过程”。 
然后,再从十位数字开始进行同样的操作,直到最高位。 
这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

下面给出图示,帮助理解基数排序的过程。


下面给出示例代码(Java版,适用于正整数排序):
package Test;

import java.util.Arrays;

public class JiShuPaiXu {
/**
   * @param number 给定一个数字
   * @param order 指定要获取的位数,取值应该大于0(个位用1表示,十位用2表示,以此类推)
   * @return 返回指定位置上的数字,合理的返回结果是0~9共十种可能的结果
   */

public static int digit(int number, int order){
int digit = number;  //如果是0-9的数,不进入while
while(--order>0)digit/=10;  //++i返回的是i自加后的内容,i++返回的是i未自加的内容
//返回商
return digit%10;
//返回余数
}

/**
   * 返回给定数字的位数
   */

public static int maxOrder(int number){
int order=1;
while ((number/=10)!=0)order++;
return order;
}

public static void radixSort(int[] numbers){
int radix=10;
int size =numbers.length;
int[][] bucket=new int[radix][size]; //桶数组,radix为桶的个数
//size为桶的大小
int[] counter=new int[radix];  //记录每个桶中保存的数据个数

int maxNum=numbers[0];
for (int num:numbers)    //
if (num>maxNum)maxNum=num;
//而在Java语言的最新版本――J2SE 1.5中,引入了另一种形式的for循环。借助这种形式的for循环,现在可以用一种更简单地方式来进行遍历的工作。第二种for循环
//不严格的说,Java的第二种for循环基本是这样的格式:
//for (循环变量类型 循环变量名称 : 要被遍历的对象) 循环体

int maxOrder=maxOrder(maxNum);// 计算序列中最大数的位数

// 基数排序分为分配过程和收集过程两大步
for (int i=1;i<=maxOrder;i++){
// 分配过程(数据序列装入桶中)
for (int j=0;j<size;j++){
int row=digit(numbers[j],i);
int col=counter[row]++;
bucket[row][col]=numbers[j];
}
// 收集过程(从桶中取出数据)
int j=0;
for (int r=0;r<radix;r++){
for (int c=0;c<counter[r];c++)
numbers[j++]=bucket[r][c];
counter[r]=0; //个位,,十位,,百位这些的counter是不一样的
//一位弄完必须清零
}
}

}

public static void main(String[] args) {
// TODO Auto-generated method stub
int[] numbers = {73, 22, 93, 43, 55, 14, 28, 9};
   radixSort(numbers);
   System.out.println(Arrays.toString(numbers));
}

}
结果:





原创粉丝点击