一.希尔排序
- 主要思想:通过在每一轮设置索引的增量间隔(随轮数增加不断有规律地缩小)来对每一组间隔的两个元素进行比较排序,直到增量间隔为1。
- 步骤概述:
1.设置增量间隔gap的for循环; 2.设置处理每一组gap间隔的元素的for循环; 3.通过移位的方式比较排序这两个gap间隔的元素。 - 模板代码:
public static void shellSort(int arr[]){
for(int gap = arr.length / 2;gap > 0;gap /= 2){
for(int i = gap; i < arr.length;i++){
int j = i;
int temp = arr[j];
if(arr[j] < arr[j - gap]){
while (j - gap >= 0 && arr[j] < arr[j - gap]){
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
}
- 示例理解:
二.插入排序(从小到大为例)
-
主要思想:把一组元素看成两组,左边为已经有序的,右边为待排序的,然后通过依次让右边的每一个元素与左边从右到左的有序元素作比较,直到找到那个比这个右边的元素大的元素的位置,即为插入位置。 -
步骤说明: 1.把0索引元素作为左边的有序组,从1索引开始遍历右边待排序组的for循环; 2.for循环中注意暂存当前的那个右边组的元素值; 3.while循环条件,如果右边的元素要插入左边,先从左边组的最后一个元素开始比较,由于是从小到大排序,而且左边组已经是有序的,即这个元素在左边组最大。既然想插入到左边组,要么比这个左边元素小,一直找,直到找到插入位置;要么比这个左边元素大,不动。 4.由于while的递减操作会最终多减去1,则要在该循环外的插入位置索引加1再插入暂存的元素值。 -
示例图解: -
代码模板:
public static void insertSort(int arr[]){
for(int i = 1;i < arr.length;i++){
int inserVal = arr[i];
int inserIndex = i - 1;
while(inserIndex >= 0 && arr[inserIndex] > inserVal){
arr[inserIndex + 1] = arr[inserIndex];
inserIndex--;
}
arr[inserIndex + 1] = inserVal;
}
}
三.堆排序(以大顶堆为例)
- 基础知识回顾:
1.堆:即完全二叉树,可由数组按编号存储。左子节点对应2i+1节点,右子节点为2i+2,父节点为(i-1)/2。例如数组[5, 1, 7, 2, 8, 6, 3, 9, 4],其堆图如下: 2.大顶堆:指每个根节点都比其子节点大,即第一个根节点就是最大的完全二叉树(每一层从左往右编号不中断的二叉树)。大顶堆图示如下: - 主要思想:
1.大顶堆调节方法:先暂存指定的非叶子节点,然后让该节点与其子节点进行比较并排序,使其构成大顶堆结构。当左右的任一个子节点被与这个父节点交换,则以这个被交换下来的父节点为新的父节点,继续变成大顶堆的操作,最终形成以最初的暂存非叶子节点位置为父节点的局部大顶堆结构。 2.堆排序方法:基于大顶堆的结构,知道根节点是最大值,那就可以不断缩小遍历范围,让每次缩小范围内的最后一个节点与当前根节点进行交换,即让每次缩小范围的最后一个节点都变成最大值,并对每次缩小范围内的所有节点,从根节点开始,再次重构成大顶堆结构。 - 代码模板:
1.调节大顶堆:
public static void adjustHeap(int[] arr,int i,int length){
int temp = arr[i];
for(int index = i * 2 + 1;index < length;index = index * 2 + 1){
if(index + 1 < length && arr[index] < arr[index + 1]){
index++;
}
if(arr[index] > temp){
arr[i] = arr[index];
i = index;
}
}
arr[i] = temp;
}
2.堆排序:
public static void heapSort(int[] array){
int temp;
for(int i = array.length/2 - 1;i >= 0;i--){
adjustHeap(array,i,array.length);
}
for(int j = array.length - 1;j > 0;j--){
temp = array[j];
array[j] = array[0];
array[0] = temp;
adjustHeap(array,0,j);
}
}
四.基数排序
- 主要思想:准备10个桶,每个桶是一个一维数组。首先比较待排序数组的每一个元素的个位数与这10个桶中的哪一个桶的索引对应相等,然后将这个元素放入该桶中,遍历完所有元素后,又将放入了桶中的元素按桶索引依次放回原来的数组。之后依次每一轮比较的是元素的十位,百位…,重复以上操作。(对于位数不够的补0,例如数组[4,10],当比较十位数时,4就看成04,即其十位数为0)。
- 图解示例第一轮(数组[53,3,542,748,14,214]):
- 代码模板:
public static void radisSort(int[] arr){
int max = arr[0];
for(int i = 0;i < arr.length;i++){
if(arr[i] > max){
max = arr[i];
}
}
int maxLength = (max + "").length();
int[][] bucket = new int[10][arr.length];
int[] bucketElementCounts = new int[10];
for(int i = 0, n = 1;i < maxLength;i++, n *= 10){
for(int j = 0;j < arr.length;j++){
int digitOfElement = arr[j] / n % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
int index = 0;
for(int k = 0;k < bucketElementCounts.length;k++){
if(bucketElementCounts[k] != 0){
for(int l = 0;l < bucketElementCounts[k];l++){
arr[index++] = bucket[k][l];
}
}
bucketElementCounts[k] = 0;
}
}
}
五.选择排序(升序为例)
- 主要思想:首先将数组中一个元素自定义为最小的元素(一般从左到右选,故第一次选的就是首元素),然后让该元素与剩余元素进行比较,遇到比该元素小的就更换一开始选定的最小元素为这个较小的元素,一轮比较完所有元素后,就将这个最小元素与首元素交换。之后,依次选定剩余的元素为最小元素,每一轮进行同样的比较操作并与选定首元素进行交换。
- 图解示例及说明:
1.第一趟排序:选定第一个元素7为最小元素,将其与剩余元素进行比较完后,知剩余元素中最小元素为-2,则交换第一个元素7(主要思想提到的选定的首元素)与最小元素-2。 2.第二趟排序:选定第二个元素4为最小元素,将其与剩余元素进行比较完后,知剩余元素中最小元素为4,此时的最小元素就是选定的元素,则不进行交换操作。 余下每趟排序分析同理,不赘述。 - 代码模板:
public static void chooseSort(int[] arr){
for(int i = 0;i < arr.length;i++){
int minIndex = i;
int min = arr[i];
for(int j = i + 1;j < arr.length;j++) {
if (min > arr[j]) {
min = arr[j];
minIndex = j;
}
}
if(minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
六.归并排序(以升序为例)
- 主要思想:分而治之
1.分:将所有元素不断按对半拆分成多组,直到分成每一个单元素为一组。 2.治:分的逆操作,即合并。同时,在不断合并每一组元素时,每合并一次都对合并的元素进行排序操作。 - 图解示例及说明:
图二分析如下(最后一次合并): 1.合并之后,分成左右两半看待,由于左边的4大于右边的1,则将较小的1放入暂存数组,然后右边索引右移。(可以理解为那个被放到暂存数组中的元素,被从原数组中取出,则要考虑下一个元素,即移动的地方是被放入暂存数组中的元素的位置)。 2.第一步是右边移动的情况,这第二步是左边移动的情况。由于左边的4小于右边的6,故分析同第一步,较小的4放入暂存数组,左边索引右移。 3.考虑到可能左边或右边部分会出现剩余未比较的元素,则直接将全部按顺序放入暂存数组。 注意点:左半部分和右半部分都是已经有序的,因为合并时,从两个单元素开始合并,它们各自本身就是一个有序数组,接着按同样的规律操作而形成的更长的左右半数组也就是有序的。 - 代码模板:
public static void merge(int[] arr,int left,int right,int mid,int[] temp){
int t = 0;
int i = left;
int j = mid + 1;
while (i <= mid && j <= right){
if(arr[i] < arr[j]){
temp[t] = arr[i];
t++;
i++;
}else{
temp[t] = arr[j];
t++;
j++;
}
}
while(i <= mid){
temp[t] = arr[i];
t++;
i++;
}
while(j <= right){
temp[t] = arr[j];
t++;
j++;
}
t = 0;
int tempLeft = left;
while (tempLeft <= right){
arr[tempLeft] = temp[t];
tempLeft++;
t++;
}
}
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if(left < right){
int mid = (left + right) / 2;
mergeSort(arr,left,mid,temp);
mergeSort(arr,mid + 1,right,temp);
merge(arr,left,right,mid,temp);
}
}
七.快速排序(以升序为例)
- 主要思想:首先选定一个基准元素a,然后将这个元素前面的元素依次与它进行比较,直到找到比它大的元素b。再将这个元素后面的元素依次与它进行比较,直到找到比它小的元素c。此时,将b与c交换位置。
- 图解示例及说明:
以首元素6为基准值,从尾元素开始向左遍历并比较。 j向左遍历到比6小的5就停住,i向右遍历到比6大的7就停住,5和7交换位置。此时序列为6 1 2 5 9 3 4 7 10 8。 同理,之后要交换左边的9和右边的4,然后i和j遍历到相同位置3处,此时第一轮遍历完成,将基准值6与3交换位置,即6已经到达正确位置。 然后在6的左边序列和右边序列分别选定基准值,再进行同样遍历比较操作。 - 代码模板:
public static void quickSort(int[] arr,int left,int right){
int l = left;
int r = right;
int temp;
int pivot = arr[(left + right)/2];
while(l < r){
while (arr[l] < pivot){
l++;
}
while(arr[r] > pivot){
r--;
}
if(l >= r){
break;
}
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
if(arr[l] == pivot){
r--;
}
if(arr[r] == pivot){
l++;
}
}
if(r == l){
r--;
l++;
}
if(r > left){
quickSort(arr,left,r);
}
if(l < right){
quickSort(arr,l,right);
}
}
理解死循环加一和减一:基准值是要始终保持在[r,l]这个闭区间的,这一点应该能理解,因为交换的都是它左右两边的值,r和l最多也就刚好处在基准值的位置上。如果arr[r]等于pivot,然后通过r++这样移动来突破死循环,会使pivot处用[r,l]之外,同理,在arr[l]等于pivot,l–也一样,所以这样不对,应该是代码模板中的样子。
八.冒泡排序(以升序为例)
- 主要思想:按下标顺序依次让待排序序列的每一个元素与其相邻元素(下一个)比较,若发现逆序则交换。
- 图解示例
- 代码模板:
public static void bubbleSort(int[] arr){
int temp;
boolean flag = false;
for(int i = 0;i < arr.length - 1;i++){
for(int j = 0;j < arr.length - 1 - i;j++{
if(arr[j] > arr[j + 1]){
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if(!flag){
break;
}else{
flag = true;
}
}
}
|