数据结构之二分法以及八大排序算法的理解与代码实现(Java)

本文详细介绍了二分法的原理及Java实现,并逐一讲解了冒泡排序、快速排序、直接插入排序、希尔排序、简单选择排序、堆排序、归并排序和基数排序这八大排序算法的思想和代码实现。

二分法

      顾明思意,二分法就是将我们的数组一分为二,然后拿我们要查找的数去和中间的那个元素比较,如果刚好等于中间那个元素,那么就返回中间位置的下标。如果比中间的元素小,那么再使用相同的方式从中间元素的前面部分去找;相反如果比中间元素更大,那么就在中间元素后面的部分去找。如此循环,这样就叫二分法,相信大家都意识到了,二分法之所以能这样做的前提是:数组已经是有序的(通常情况下是升序)。下面看一下二分法的具体实现:

在这里插入图片描述
以上图片就是整个二分法的实现过程,从上面可以看出,二分法排序关键是要确定中间元素的下标,那么中间元素的下标怎么来确定呢?在我们初始化的时候,定义一个整形变量start和一个end,分别指向数组的头和尾,那么中间元素的下标就是:(start + end)/2,然后比较中间元素和要查找元素的大小。如果此时中间的元素等于要查找的元素,那么就直接返回下标。如果要查找的元素比中间元素小,那么让end = middle - 1;相反,如果比中间元素大的话,就让start = middle + 1。 为什么要这样做呢,我们仔细想一想,中间元素的下标是不是每次都在变,而middle = (start + end) / 2,所以start 和end 实际上就是用来控制middle的。利用循环来使middle不断地改变,直到start < end的时候结束。下面是具体代码实现:

/**
array:被查找的数组,默认升序
number:被查找的元素
*/

public int EFSort(int[] array,int number){
     //定义起始位置
     int start = 0;
     //定义结束位置
     int end = array.length - 1;
     while(start < end){
     //每次循环让 中间元素下标等于(start + end) / 2 
      int middle = (start + end) / 2;
     //如果要查找的元素和中间元素相等,直接返回middle
      if(array[middle] == number){
         return middle;
       }else{
        //如果中间元素比number大,那么去前面找
         if(array[middle] > number){
            end = middle - 1;
          }else{
            //否则就去后面找
            start = middle + 1;
           }
       }
     }
     return -1;
}

排序算法之:冒泡排序

   冒泡排序的原理其实非常简单:当我们拿到一个数组过后,从头开始,第一个元素与第二个元素比较,把大的放后面,小的放前面;然后第二个元素与第三个元素比较,大的放后面,小的放前面;然后第三个与第四个比较;第四个与第五个比较......如此下去直到把数组中最大的那个数放到数组的末尾。这样就完成了第一堂冒泡排序。然后进行下一轮比较,这时,上一轮比较出来的最大的元素就不用参与比较了。所以冒泡排序每轮的比较次数都在减少。看一个例子:
   数组array[4,5,2,1,9,8,6]
   第一趟:4和5比,不要用交换;5和2比,交换[4 , 2 , 5 , 1 , 9 , 8 , 6] ;5和1比:交换[4 , 2 , 1 , 5 , 9 , 8 , 6] ;5和9比,不用交换;9和8比,交换[4 , 2 , 1 , 5 , 8 , 9 , 6] ;9和6比,交换[4 , 2 , 1 , 5 , 8 , 6 , 9];这样第一趟就把最大的元素排到了最后面,可以看出第一次只比较了6次
   第二趟:4和2比,交换[2 , 4 , 1 , 5 , 8 , 6 , 9];4和1比,交换[2 , 1 , 4 ,  5 , 8 , 6 , 9];4和5比,不用交换;5和8比,不用交换;8和6比:交换[2 , 1 , 4 , 5 , 6 , 8 , 9];8就不用和9比,所以比较次数比上一轮少一次。
   第三趟、第四趟.......这样数组最终会被排序成[1 , 2 , 4 , 5 , 6 , 8 , 9]
public int[] bubbleSort(int[] array){
//第一堂的比较次数最多:array.length - 1
   for(int i = 0;i < array.length - 1;i++){
     //每一趟的比较次数都比上一次少1
     for(int j = 0;j<array.length -1 - i;j++){
           if(array[j] > array[j+1]){
               int temp = array[j+1];
               array[j+1] = array[j];
               array[j] = temp;
           }     
     }
   }
   return array;
}

排序算法之:快速排序

   快速排序很多时候称之为快排,他的核心思想呢就是:定义一个标准数,然后比标准数小的全部放在数组左边,比标准数大的全部放在数组右边(当然也可以反过来)。通常情况下我们将数组的第一个元素作为一个标准数,然后再用两个变量start、end分别指像数组的头和尾,因为标准数定义在数组的第一个元素,所以我们从右边开始遍历数组当遇到比标准数小的时候我们就执行 array[start] = array[end];然后从左边开始遍历找到比标准数大的元素执行:array[end] = array[start]; 注意遍历的时候如果不满足条件就start ++ 或则end --。 最后start和 end 相等。这时就把之前定义的标准数放在这儿:array[start] = 标准数。
   比如有一个数组:array [2,8,6,1,0,3,7,4];
           1、标准数:int stand = array[0];   //stand = 9
           2、start = 0,end = array.length - 1;
           while(start < end){
                //从右边开始,直到找到比标准数小的
                while( array[end] >=stand && start < end ){
                     end--;
                 }
                 //while循环结束后,表示已经找到比标准数小的数了,所以现在要做的就是把这个值赋给 start 所指向的地方
                 array[start] = array[end];
                 //然后从左边开始,直到找到比标准数大的,放在数组右边
                 while( array[start] <= stand && start < end ){
                    start++;
                 }
                   //while循环结束后,表示已经找到比标准数大的数了,所以现在要做的就是把这个值赋给 end 所指向的地方
                   array[end] = array[start];
             
           }
           //最外层while循环结束,表示start = end
           这是用标准数来代替这个位置
           array[start] = stand;
 这就是第一趟快排,上面的数组经过这样的操作后会变成这个样子: array[0,1,2,6,8,3,7,4]
 可以看到2左边的数都比2小,2右边的数都比2大。这时候利用递归把2的左边、右边在使用快排,这样数组就被排序好了。下面用代码来实现:
/**
array:需要排序的数组
start:开始下标
end:结束下标
当start和end相等时,即使递归结束条件
*/
public void quickSort(int[] array,int start,int end){
   if(start < end){
    //默认标准数为start所值的元素
    int stand = array[start];
    //分别用两个变量来表示start和end
    int startPoint = start;
    int endPoint = end;
    while( startPoint < endPoint ){
    //从右边开始,直到找到比标准书小的数
         while(array[endPoint] >= stand && startPoint < endPoint){
                  endPoint--; 
         }
         //把这个比标准数小的数赋值给startPoint对应的地方
         array[startPoint] = array[endPoint];
     //从左边开始,直到找到比标准书大的数
         while(array[startPoint] <= stand && startPoint < endPoint){ 
                  startPoint++;
         }
         //  //把这个比标准数大的数赋值给endPoint对应的地方
         array[endPoint] = array[startPoint];
    }
    //这是startPoint = endPoint,把标准值赋值给他们指向的位置
         array[startPoint] = stand; //或者array[endPoint] = stand;
    //然后调用递归将左半边继续采用快排:
    quickSort(array,start,startPoint);
    //继续对右半边快排:
    quickSort(array,startPoint+1,end);
   }
}

排序算法之:直接插入排序

直接插入排序的思路比较简单:首先我们拿到一个数组,从它的第二个元素开始,再用一个循环来遍历它之前的元素,如果他之前的元素比他大,那么就交换。
在这里插入图片描述
以上图片是直接插入排序的实现思路,下面通过代码来实现一下:

public void  insertSort(int[] array){
 //从第二个元素开始
   for(int i = 1;i<array.length;i++) 
     //遍历第i个元素之前的数据,依次比较大小
     //用一个临时变量来存储第i个元素
     int temp = array[i];
     for(int j = i - 1;j >= 0 && array[j] >= array[j+1];j--){
         array[j+1] = array[j];
         array[j] = temp;
   }
}

排序算法之:希尔排序

希尔排序也是非常有意思的一种排序算法,他是基于直接选择排序进行优化得到的一种算法。他的关键思想就是利用步长来对数进行分组,然后对这些分组进行直接插入排序。
在这里插入图片描述
下面使用代码来实现希尔排序:

public void shellSort(int[] array){
   //定义步长
   int d = array.length / 2;
   while( d != 0 ){
     //内部使用直接插入排序,因为使用了步长,所以起始位置不再是1,而是步长d
      for(int i = d;i<array.length;i++){
          int temp = array[i];
          for(int j = i -d;j >= 0 && array[j] >= array[j+d];j = j-d ){
                array[j+d] =array[j];
                array[j] = temp;
         }
      }
      //步长为原来的半
      d = d/2;
   }
}

排序算法之:简单选择排序

简单选择排序的思路比较简单,从第一个元素开始,然后默认他是最小的数并且用整型变量index记录位置,然后从它开始遍历,遇到比他小的,就让把这个数的下标赋值给index,这样第一次循环下来,index下标对应的数就是最小的那个数。然后把它赋值给本次循环开始的位置。第二次循环就从数组的第二个元素开始,用相同的方法进行遍历。直到从最后一个元素开始,这样数组就被排序好了。下面我们看一下具体的代码:

public void simpleSelectSort(int[] array){    
    for(int i = 0;i < array.length;i++){
        int index = i;
        //从当前这个元素开始向后遍历,i前面的元素已经排序好了
        for(int j = i;j<array.length;j++){
             if(array[j] < array[index]){
               index = j;
          }
      }
       //最后把index对应的元素和i对应的元素交换
       int temp = array[i];
       array[i] = array[index];
       array[index] = temp;
   }
}

排序算法之:堆排序

在介绍堆排序之前我们先来回顾一下二叉树相关的知识:
1、二叉树:指的是树里面每个节点的子节点数都小于等于2
2、满二叉树:所有的叶子节点都在最后一层,总节点数:2的n次方-1(n表示树的高度)
3、完全二叉树:所有的叶子节点都在最后一层或倒数第二层,并且,叶子节点在倒数第二层右连续,在最后一层左连续,且对于第n个节点而言:
其左儿子节点:2n + 1,其右儿子节点:2n + 2,其父节点:(n-1)/2
二对于一棵二叉树而言,如果每个双亲节点的权重都比其子节点的权重大,那么称这个二叉树为大顶堆(左),反正称为小顶堆(右)
在这里插入图片描述
由于我们是对数组进行排序,而数组是连续的存储结构,所以我们要使用完全二叉树的性质,首先我们要找到最后一个非叶子节点的位置,利用完全二叉树的性值,数组的最后一个叶子节点的下标为array.length - 1,则他的父节点就是最后一个非叶子节点。所以最后一个非叶子节点的下标为: (array.length - 2) /2,找到最后一个非叶子节点以后,将他的权值和它的两个子节点比较,如果他的权值比两个子节点的都大,就不用进行交换,否则需要交换。下面用代码来具体演示:

/**
array:需要排序的数组
size:待排序数组的大小
index:开始比较的位置
*/
public void maxHeap(int[] array,int size,int index){
      if(index <= size){
      //左儿子节点的下标
      int leftNode = 2 * index + 1;
      //右儿子节点的下标
      int rightNode = 2 * index + 2;
      int max = index;//默认父节点的权重最大
      //判断左儿子存不存在以及权重是否大于夫节点:
      if(leftNode <= size && array[leftNode] > array[index]){
           max = leftNode;
      }
       //判断右儿子儿子存不存在以及权重是否大于父节点:
      if(rightNode <= size && array[rightNode] > array[index]){
          max = rightNode;       
      } 
      //交换
      int temp = array[max];
      array[max] = array[index];
      array[index] = temp;
      //交换以后,可能改变子节点这棵树的顺序,所以还要对子节点这个树进行递归
      maxHeap(array,size,max);
      }
     }

 public void heapSort(int[] array){
     
     //先找到最后一个非叶子节点
     int index = (array.length - 2)/2//先将数组变成一个大顶堆
     for(int i = index,i >= 0;i--){
         maxHeap(array,array.length - 1;i)
    }   
    //然后将堆顶的元素赋值给数组最后面的元素
    for(int j = array.length - 1;j > 0;j--){
    //交换
     int temp = array[j];
     array[j] = array[0];
     array[0] = temp;
     maxHeap(array,j-1,0);
    }
 }

排序算法之:归并排序

在归并排序之前先了解一下归并的原理:把两个有序的数组归并成一个数组:如下图:
在这里插入图片描述
下面用代码来具体实现一下:

/**
low:从哪儿开始归并
middle:从哪儿开始将数组分割成两个数组
hign:归并范围的临界值
*/
public void merge(int[] array,int low,int middle,int hign){
 
 //首先我们需要定义一个新数组来存放归并后的结果,这里我们借助middle来将array抽象成两个数组 第一个 [low,middle] 第二个[middle+1,hign]
 int[] newArray = new int[hign - low + 1];
 //第一个数组起始位置
 int lowPoint = low;
 //第二个数组起始位置
 int startPoint = middle + 1;
 //新数组的下标
 int index = 0;
while( lowPoint <= middle && startPoint <= hign ){
      if(array[lowPoint] <= array[startPoint]){
         newArray[index] = array[lowPoint];
         lowPoint++;
     }else{
         newArray[index] = array[startPoint];
         startPoint++;
    }
    index++;
 }
 //把剩下的元素再追加到newArray中,因为两个分割出来的数组长度可能不一样
 while( lowPoint <= middle ){
      newArray[index] = array[lowPoint];
      lowPoint++;
      index++;
 } 
  while( startPoint <= hign ){
      newArray[index] = array[startPoint];
      startPoint++;
      index++;
 } 
 //再把新数组赋值给旧数组
   for(int i = 0; i < newArray.length;i++){
        array[low+i] = newArray[i];
   }
}

//除此之外我们还需要一个函数来将待排序的数组进行无线分割,分割成有序的状态,如下图:
public void mergeSort(int[] array,int start,int end){
    //递归条件:起始位置小与末位,如果两个变量相等了,没必要再分割了
    if(start < end ){
    //定义分割位置
    int middle = (start + end)/2;
    //利用递归分割左边
    mergeSort(array,start,middle);
    mergeSort(array,middle+1,end);
    merge(array,start,middle,end);
    }
}

在这里插入图片描述

排序算法之:基数排序

基数排序算法的核心是:第一次循环先用个位来将数进行分组,然后将这些书又放回原数组,再利用十位进行分组如此下去,直到用这些书最大的位数来进行分组后,排序结束。可以看出基数排序的循环次数取决于最大的那个数是几位数。
在这里插入图片描述下面用代码来实现一下:

public void radixSort(int[] array){
   //定义一个最小数
      int max = Integer.MIN_VALUE;
   //找到数组中最大的元素
     for(int i : array){
      if(i > max){
        max  = i;
       }
     }
     //可以把上面的是个筒子看成一个二维数组,数组的行代表筒的下标,数组列代表元素个数,最多也就数组的长度
     int[][] temp = new int[10][array.length];
     //因为要进行不断的取放,所以还要定义一个一维数组来标识对应筒子里面有对少个元素,而且还可以记录下标
     int[] record = new int[10];
     //判断最大数的位数,决定循环次数
     int ws = (""+max).length();
     for(int j = 0, n=1;j < ws;j++,n = n*10 ){ 
          //遍历每一个元素
          for(int k = 0;k < array.length; k++){
              int ys = array[k]/n%10;
              temp[ys][record[ys]] = array[k];
              record[ys]++;
         } 
         //取出来:
         int index = 0;
         //遍历余数对应的筒子
         for(int l = 0;l < record.length; l++){

             if(record[l] != 0){
               for(int m = 0;m < record[l];m++){
                   array[index] = temp[l][m];
                   index++;
               }
               //将record重置为0,下一轮循环要对十位进行操作
               record[l] = 0;
             }
         }
     }
}

以上就是我个人对二分法以及八大基本排序算法的理解,欢迎各位小伙伴不吝赐教,提出优化建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值