文章目录
1.冒泡排序
冒泡排序(Bubble Sort):一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
假设一个无序数组的长度N,利用冒泡排序将其从小到到进行排序:
基本思路:
1)要进行N – 1趟,此处趟数用i来表示
2)每一趟中要进行N – i次(i表示第几趟,从而表明了趟数 + 每一趟中进行交换的次数 == N)
3)进行前后比较,如果顺序错误,那么就要将其交换过来
由图可以知道,一共有7个数,最多进行了 7 – 1 = 6 趟,在第i趟中进行了N – i次交换
代码实现:
for(int i = 0; i<arr.length - 1; i++){
for(int j = 0; j<arr.length - i-1; j++){
if(arr[j+1] < arr[j]){ //如果顺序错误,那么进行交换
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
冒泡排序的优化:由上面可以知道,走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。所以只要有一个标记flag,在每一趟中都没有进行过一次交换,那么说明了数组有序了,不需要再进行下一趟的交换。
for(int i = 0; i<arr.length - 1; i++){
flag = false;//注意要将flag重置为false
for(int j = 0; j<arr.length - i-1; j++){
if(arr[j+1] < arr[j]){
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
flag = true;//说明已经交换
}
}
if(flag == false)//如果这一趟没有进行过交换,那么就说明了这一次排序使得数字有序,退出循环
break;
}
2.选择排序
选择排序(SelectSort):一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
算法步骤
1)在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3)重复第二步,直到所有元素均排序完毕。
说明:
1)最多进行了arr.length – 1趟交换
2)在每一趟排序中,需要找到最小值,然后和当前这个数进行比较,如果当前的值比最小值要大,那么要交换,否则不用(此处是按照升序进行排序),进入下一趟排序(就好比上面图中的20,要遍历其后面的数,找到最小值,然后将其交换)
代码实现:
int minIndex;//最小值对应的下标
int min;//表示最小值
for(int i = 0; i<arr.length; i++){
minIndex = i;
min = arr[i];
for(int j = i+1; j<arr.length;j++){
if(arr[j] < min) {
minIndex = j;//表示最小值的下标
min = arr[j];//表示最小值
}
}
if(minIndex != i){
//如果min不是i,那么就说明当前下标的后面还有数比当前的下标对应的数字要小,所以进行交换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
3.插入排序
插入排序(InsertSort):
插入排序算法思想:每趟将一个元素,按照其关键字的大小插入到它前面已经排序的子序列中,依此重复,直到插入全部元素。
步骤:
1)将从下标为1的数字开始遍历,将当前的数字赋给临时变量temp
2)将temp和当前下标(temp对应的下标)之前的数字进行比较,如果temp大于等于前面的数字y,那么表明了y的后面就是temp要插入的位置,否则就将前面的数字后移
代码实现:
//这个代码是一边输入,然后一边进行插入排序的
for(int i = 0; i<arr.length; i++) {
arr[i] = (int)(Math.random()*800);//产生8000个[0,800)的随机数
temp = arr[i];//将arr[i]赋给临时变量num,然后拿num这个临时变量和arr[i]之前的元素进行比较(要根据需求来比较)
for(j = i - 1; j>=0; j--){
if(arr[j] <= temp)//当num这个临时变量比arr[j]要大,那么就说明j后一个元素就是num
break;
arr[j + 1] = arr[j];//否则就要将i前面的元素后移
}
arr[j+1] = num;//注意是j+1,而不是j,因为num比j下标对应的值大或者相等
}
//先输入完毕,再进行排序
for(int i = 1; i<arr.length; i++) {
num = arr[i];//将arr[i]赋给临时变量num,然后拿num这个临时变量和arr[i]之前的元素进行比较
for(j = i - 1; j>=0; j--){
if(arr[j] <= num)//当num这个临时变量比arr[j]要大,那么就说明j后一个元素就是num
break;
arr[j + 1] = arr[j];//否则就要将i前面的元素后移
}
arr[j+1] = num;//注意是j+1,而不是j,因为num比j下标对应的值大或者相等
}
可能大家会疑惑,为什么要将当前的数字赋给临时变量temp呢?如果不赋的话(那下面的代码说明一下),如果arr[i]不赋值给temp,那么如果arr[i]小于其前面的数字,那么会后移,arr[i]的值就变了,所以需要将arr[i]赋值给temp
4.折半插入排序
折半插入排序的原理和折半查找类似的,具体的定义可以百度进行搜索。
所以根据它的定义,我们在一个有序数组中找到新元素插入的位置t,然后将这个插入位置后的元素往后移,从而给这个新元素插入腾出一个位置,然后将新元素插入到位置t,从而实现有序.
具体的代码:
public class Demo01 {
public static void main(String[] args) {
int[] arr = new int[]{ 8,7,6,9,0,1,3,635,34232,123,89,8};
System.out.print("没有进行排序时:");
display(arr);
binarySort(arr,arr.length);//调用方法,从而进行折半插入排序
System.out.print("进行排序之后:");
display(arr);
}
public static void display(int[] arr){
for(int num: arr){
System.out.print(num + " ");
}
System.out.println();
}
public static void binarySort(int[] arr,int n){
int i,j,tmp;//tmp用于存放临时变量
//low表示的下标为i的前面有序数组的起点下标,high表示的是下标为i之前的有序数组的起点终点
int low,high,mid;//mid表示子数组中的中间下标
for(i = 1; i < n; i++){
tmp = arr[i];
low = 0;
high = i - 1;
while(low <= high){
//如果至少含有一个元素的时候就继续循环,进行比较,获得tmp要插入的位置
mid = (low + high) / 2;
/* 如果代码这样写的话,会怎样咧? 事实上,如果输入的数组没有重复的数字,那么的确没有什么影响 但是输入的数组含有重复数字的话,那么就没有办法更新low或者 high的值了,进而会陷入死循环中,从而造成超时 if(arr[mid] > tmp) high = mid - 1; else if(arr[mid] < tmp) low = mid + 1; */
if(arr[mid] > tmp){ //如果tmp小于中间值,就说明tmp在左边,所以更改high的值
high = mid - 1;
}else {
/* 否则,如果tmp大于等于中间值,说明tmp在右边,更改low的值 (一定是else,没有else if了,如果是else if的话,会造成死 循环) */
low = mid + 1;
}
}
/*当low> high,这时候就意味着没有了元素,这时候可以退出循环 了,并且high的后面都是大于tmp的元素(也就是说high + 1这个 下标对应的数是最后一个大于tmp的数),因此需要将这些元素后移, 这里的for循环之所以是从j = i - 1开始,是因为下标为i前面的有 序数组是[0 , i - 1],那么我们现在需要将大于等于tmp的元素往后 移,给tmp插入腾出位置,所以是从i - 1开始遍历,往后移,直到 j = low - 1的时候结束后移) 当然,这里的for循环的循环判断条件也可以是j >= low,因为退出 while循环的时候,low必然大于high,并且low - high = 1(这个结 论大家可以尝试者用一组数据进行证明,例如1 2 3 4 5 6 7, 如果当前下标i对应的值是7,也就是说他前面的有序数组有6个数, 如果当前下标i对应的值是6,那么他前面的有序数组有5个数) */
for(j = i - 1; j >= low; j--)
arr[j + 1] = arr[j];
arr[j+1] = tmp;
}
}
}
运行结果:
4.希尔排序
希尔排序(ShellSort):也是一种插入排序,他是简单插入排序改进之后的一个更加高效的版本,也称为缩小增量排序。
算法描述:
1)将一个数据序列分成若干组,每组由若干相隔一段距离(称为增量)的元素组成,在一个组内采用直接插入排序算法进行排序。
2)增量初值通常为数据序列长度的一半,以后每趟增量减半,最后值为1,此时就成为了直接插入排序了。随着增量逐渐减小,组数也减小,组内元素个数增加,数据序列接近有序。
当增量gap变成了1,此时就只有一组,变成了直接插入排序(增量为1),当gap 为0时,数组实现了有序。
代码实现:
int gap= arr.length / 2;//步长
int j,temp;
while(gap != 0){ //当gap等于0的时候,退出循环,数组实现有序
for(int i = gap; i<arr.length; i++){
temp = arr[i];//将arr[i]赋给一个临时变量,然后和其同一组中并且在i前面的数字进行比较
for(j = i-gap; j>=0 ; j-=gap){ //这一步近似于插入排序,将temp和i前面的而且是同一组的数字进行比较
if(arr[j] <= temp){ //此处是升序排序
break;
}
arr[j+gap] = arr[j];//如果temp小于arr[j]的值,那么后移(注意这里不是arr[j + 1] = arr[j]了哈,因为要在每一组中进行直接插入排序嘛)
}
arr[j+gap] = temp;//注意这一步是arr[j + length],为什么不是j呢?上面的插入排序已经说明清楚了,这里就不说了哈
}
gap /= 2;
}
5.归并排序
归并的含义:将两个或两个以上的有序表
合并成一个新的有序表。
归并排序(MergeSort):采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
递归实现归并排序
其算法思路如下:
1.如果给的数组只有一个元素的话,直接返回(也就是递归到最底层的一个情况)—作为结束递归的条件
2.把整个数组分为尽可能相等的两个部分(分)
3.对于两个被分开的两个部分再次进行操作2(也就是递归)
4.把两个被分开且排好序的数组拼接在一起
代码实现:
这是我本来的代码,后来发现如果有重复的数字,就不可以,会发生报错,所以在这里标记一下,既是给自己一个提醒,也给大家一个提醒,希望大家不要像我一样犯这样的错误,哈哈哈~~~
import java.util.Scanner;
public class MergeSort {
public static int[] arr = new int[10];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
for(int i = 0; i<arr.length; i++)
arr[i] = sc.nextInt();
merge(); //调用方法,从而新建一个新的临时数组,将子数组放到这个临时数组中,然后将临时数组赋值给原来的数组,从而排好序的
System.out.println("进行排序之后:");
for(int a: arr){
System.out.print(a+ " ");
}
System.out.println();
}
public static void merge(){
//建立一个新数组,从而将子数组复制个这个数组,将所有的子数组进行归并之后,将这个数组赋值给原来的数组即可
int[] array = new int[arr.length];
mergeSort(array,0,arr.length - 1);
}
public static void mergeSort(int[] array, int n, int m){
if(n >= m){
//如果n>m,那么范围不对,如果相等,就说明只有一个元素,此时不需要进行交换
return;
}
int index = (n + m ) / 2;//将数组分成两部分
mergeSort(array,n,index);
mergeSort(array,index + 1, m);//利用递归,将一个数组分成两个子数组
merge(array,n,index,m);//将两个子数组进行归并
}
public static void merge(int[] array,int lower,int mid, int upper){
int n = upper - lower + 1;//统计这个子数组中有多少个元素
int lowerBound = lower;
int lower2 = mid + 1;//右子数组的最小下标
int j = 0;
while(lower <= mid && lower2 <= upper){
//当两个子数组都有元素的时候,则将两者的元素进行比较,谁小的谁先插入到新的数组中
if(arr[lower] <=arr[lower2]) {
array[j++] = arr[lower];
lower++;
}else{
array[j++] = arr[lower2];
lower2++;
}
}
//考虑子数组的长度不一样的情况
//当左子数组没有遍历完的时候
while(lower <= mid){
array[j++] = arr[lower++];
}
//当右子数组没有遍历完的时候
while(lower2 <= upper){
array[j++] = arr[lower2++];
}
//将已经排好序的新数组赋值给原来的数组
for(j = 0; j<n; j++,lowerBound++)
arr[lowerBound] = array[j];
}
}
改正后,实现数组含有重复数字的情况的代码:
//有重复的数字都可以进行排序的代码(改正了)
import java.util.Scanner;
public class MergeSort {
public static int[] arr = new int[10];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
for(int i = 0; i<arr.length; i++)
arr[i] = sc.nextInt();
merge(); //调用方法,从而新建一个新的临时数组,将子数组放到这个临时数组中,然后将临时数组赋值给原来的数组,从而排好序的
System.out.println("进行排序之后:");
for(int a: arr){
System.out.print(a+ " ");
}
System.out.println();
}
public static void merge(){
//建立一个新数组,从而将子数组复制个这个数组,将所有的子数组进行归并之后,将这个数组赋值给原来的数组即可
int[] array = new int[arr.length];
mergeSort(array,0,arr.length - 1);
}
public static void mergeSort(int[] array, int n, int m){
if(n >= m){
//如果n>m,那么范围不对(就好比区间[2,3],那么2肯定是比3小的,所以左边的一定比右边的小)如果相等,就说明只有一个元素,此时不需要进行交换
return;
}
int index = (n + m ) / 2;//将数组分成两部分
mergeSort(array,n,index);
mergeSort(array,index + 1, m);//利用递归,将一个数组分成两个子数组
merge(array,n,index,m);//将两个子数组进行归并
}
public static void merge(int[] array,int lower,int mid, int upper){
int n = upper - lower + 1;//统计这个子数组中有多少个元素
int lowerBound = lower;
int lower2 = mid + 1;//右子数组的最小下标
int j = 0;
//将两个子数组进行归并
while(lower <= mid && lower2 <= upper){
//当两个子数组都有元素的时候,则将两者的元素进行比较,谁小的谁先插入到新的数组中,从而实现升序排序
if(arr[lower] <=arr[lower2]) { //如果左子数组的数小于等于右子数组的数,那么先将左子数组的数插入(注意这里是小于等于,要考虑出现重复的数字的情况)
array[j++] = arr[lower];
lower++;
}else {
array[j++] = arr[lower2];
lower2++;
}
}
//考虑子数组的长度不一样的情况
//当左子数组没有遍历完的时候
while(lower <= mid){
array[j++] = arr[lower++];
}
//当右子数组没有遍历完的时候
while(lower2 <= upper){
array[j++] = arr[lower2++];
}
//将已经排好序的新数组赋值给原来的数组
for(j = 0; j<n; j++,lowerBound++)
arr[lowerBound] = array[j];
}
非递归方式实现归并排序(C语言)
#include<stdio.h>
#define MAX_SIZE 100
int array[MAX_SIZE];
void display(int arr[],int n){
int i;
for(i = 0; i < n; i++){
printf("%d ",arr[i]);
}
printf("\n");
}
void copy_arr(int arr[],int n){
int i;
for(i = 0; i < n; i++)
arr[i] = array[i];
}
void merge(int arr[],int low,int left_end,int high){
int index = low; //这一步是必须的,而不是从0开始
int right_low = left_end + 1; //获取右子数组的起点下标
while(low <= left_end && right_low <= high){
if(arr[low] <= arr[right_low]){
array[index++] = arr[low++];
}else{
array[index++] = arr[right_low++];
}
}
while(low <= left_end){
array[index++] = arr[low++];
}
while(right_low <= high){
array[index++] = arr[right_low++];
}
}
/* 利用迭代的方式,实现归并排序: 1、首先定义步长length = 1,表示子数组的长度。 2、从下标i为0开始遍历,在[i,i + length - 1],[i + length,i + 2 * length - 1] 这两个子数组调用merge方法,从而将这两个子数组进行归并排序 3、下标i不断自增,只是应该是 i += 2 * length,表示下一个子数组的起点下标 重复步骤2 4、重复步骤2,3,直到i > N - 2 * length 之所以N - 2 * length,是因为i = N - 2 * gap是最后一个满足这一次归并的下标, 如果i = N - 2 * length的话,那么左子数组[N - 2 * length,N - length - 1], 右子数组为[N - length,N - 1] 5、退出步骤4之后,需要比较i + gap和N的大小关系,如果i+length <= N,说明 这时候有两个子数组进行合并,需要再次进行合并,调用方法 merge,否则,就将array复制到arr,完成一次归并排序 6、重复上述步骤,直到length >= N */
void mergeSort(int arr[],int n,int length){
int i;
for(i = 0; i <= n - length * 2; i += 2 * length){
//左子数组[i,i + length - 1],右子数组[i + length,i + 2 * length - 1]
// printf("i = %d,i + length - 1 = %d, i + 2 * length - 1 = %d\n",i,i + length - 1,i + 2 * length - 1);
merge(arr,i,i + length - 1,i + 2 * length - 1);
}
if(i + length < n){
//如果这一步中符合条件,说明这时候有2个子数组需要进行归并排序
merge(arr,i,i + length - 1,n - 1);
}else{
/* 这一步是必须的,如果没有这一步,那么就会导致输出的时候有一部分数据 发生丢失,这时候之所以需要这样,是因为考虑到最后有些有些数据并没有 进行归并的情况,这里例子挺好的(输入的是一个奇数个数的时候) 输入21个数:1 7 4 6 9 8 2 0 3 5 10 15 16 14 18 19 17 16 14 11 20, 如果没有这一步,那么最后结果并不是升序排序 */
for(; i < n; i++){
array[i] = arr[i];
}
}
copy_arr(arr,n); //注意,这里不需要else,直接将array数组赋值给arr即可
display(arr,n);
}
void sort(int arr[],int n){
int length = 1;//定义子数组长度(理解这个含义,才可以明白mergeSort中调用merge为什么是那样子)
while(length < n){
//当子数组的长度小于n的时候,那么需要将子数组进行归并
// printf("length = %d\n",length);
mergeSort(arr,n,length);
length *= 2;//完成一次归并之后,下一次循环中,子数组的长度就是length * 2了
}
}
int main(){
int arr[MAX_SIZE];
int n,i,j,k,left_index,right_index,num,index;
scanf("%d",&n);
for(i = 0; i < n; i++)
scanf("%d",&arr[i]);
for(i = 0; i < n; i++)
array[i] = 0;
sort(arr,n);
return 0;
}
运行结果:
6.快速排序
快速排序(QuickSort): 快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。
步骤为:
1、挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),此处选择数组中间的那个数作为基准。即arr[index] = pivot,此处的index 是数组中间数的下标
2、分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,已经将数组分割成了两个子数组,左子数组都是小于基准的值,右子数组都是大于基准的值。
那么实现分割操作呢?
将基准值arr[ index ] 赋给临时变量pivot,然后分别从两边进行遍历,如果左边遇到第一个比pivot大的数,右边遇到第一个比pivot小的数,那么将两者进行交换
有同学可能又会问这里为什么又要将arr[index]赋给临时变量pivot呢?
因为在分割的过程中进行了交换,可能会使arr[index]的值发生变化,从而导致错误。比如一个例子 :一个数组{5,9,2,6,0,1,2} ,那么index = (0 + 6)/2,有pivot为6,那么从两边进行遍历,从而实现分割得到{5,2,2,1,0,6,9},那么如果不将arr[index]赋给pivot,那么arr[index]对应的值会发生改变,变为1,而不再是6了
3、递归排序子序列:通过递归,将左右两个子数组进行排序(重复上面1、2操作)—–结束递归的条件是当数组只有一个元素的时候,结束递归,此时实现有序
代码实现:
这是一个片面的代码:只能将一个不含有重复数字的数组进行排序,如果数组含有重复的数字,就会发生报错,在这里做个标记,提醒自己,也希望能够提醒大家,希望大家不要像我一样犯同样错误哦,哈哈哈~~~~
import java.util.Scanner;
public class QuickSort {
public static int[] arr = new int[10];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
for (int i = 0; i < arr.length; i++)
arr[i] = sc.nextInt();
//调用方法进行快速排序
quickSort(0, arr.length - 1);
for (int a : arr)
System.out.print(a + " ");
System.out.println();
}
public static void quickSort(int n, int m) {
if (n < m) { //因为m的下标是右边的,那么必然是大于n的,就说明不只一个元素,此时进行交换
//定义基准的下标
int index = (m + n) / 2;
int pivot = arr[index];//将中间的数作为基准,并将其值赋给临时变量pivot
int m2 = m, n2 = n;
int partition = getPartition(n,m,pivot);//分割,其中返回的值是分割的值的下标
//利用递归,将左右两个子数组进行排序
quickSort(n2, partition-1);//左子数组
quickSort(partition+1, m2);//右子数组
}else{
//如果n等于m,就说明了只有一个元素,若大于m,那么范围不对(就好比区间[2,3],那么2肯定是比3小的,所以左边的一定比右边的小)
return;
}
}
public static int getPartition(int n, int m, int pivot){
//将数组分成两个子数组,其中左子数组的值小于中间枢纽pivot的值,而右子数组的值大于中间枢纽的值
while (true) {
//当n>=m,说明了左边都是小于标注的值,这时候就可以退出循环
while (n<m && arr[n] < pivot) {
//需要在n<m的前提下进行判断,如果左边是小于标准的值,那么就右移,直到找到第一个大于标准的值的下标
//注意这里需要有n<m,否则有可能n一直移到最右边,那么很容易发生溢出,从而会报错
n++;
}
while (n<m && arr[m] > pivot) {
//需要在n<m的前提下进行判断,如果右边找到了第一个小于标准的值,那么就退出循环,否则往左移
//注意这里需要有n<m,否则有可能m一直移到最左边,那么很容易发生溢出,从而会报错
m--;
}
if (n >= m) {
//相遇了,那么就退出循环,说明基准的左边都是小于基准的数,右边都是大于基准的数,此时基准的下标为n或m,而不是原来的index
break;
} else {
swap(n,m);
}
}
return n;//返回基准的下标
}
public static void swap(int n , int m){
//没有相遇,将二者进行交换
int temp = arr[n];
arr[n] = arr[m];
arr[m] = temp;
}
}
含有重复数字的快速排序的情况分析: 这里利用三向切分进行分析的。也就是将数组分为三部分:小于当前切分元素的部分,等于当前切分元素的部分,大于当前切分元素的部分。
步骤:
1)每次切分从数组的左边到右边遍历一次,同时第一个数对应的值作为基准pivot,然后遍历,将基准后一个数字进行比较
①如果基准比其后的一个数字要大,那么直接将两者进行交换,之后将基准的下标更改,随之的其后的数字的下标也要更改
②如果基准比其后的一个数字要小,那么将其后的数字和数组最后的数字要交换,此时要将原来数组最后一个数字的下标要更改,然后再进行比较,如果还是基准的值小,那么重复②的操作,直到出现另外两种情况
③如果基准的值和其后一个数字相等,那么将基准后一个数的下标后移,注意这里的后移并不是指基准的下标的后移,而是其后的数字进行后移,下面的图可以解释
2)每次切分之后,位于gt指针和lt指针之间的元素的位置都已经被排定,不需要再去移动了。之后将(lo,lt-1),(gt+1,hi)分别作为处理左子数组和右子数组的递归函数的参数传入,递归结束,整个算法也就结束。
这时候我们对左子数组进行分析:
代码实现:
import java.util.Scanner;
public class QuickSort2 {
public static int[] arr = new int[10];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
for(int i = 0; i<arr.length; i++)
arr[i] = sc.nextInt();
System.out.println("进行快速排序之后:");
quickSort(0,arr.length - 1);
for(int a: arr){
System.out.print(a+" ");
}
System.out.println();
}
public static void quickSort(int n, int m){
if(n >= m){
//如果只有一个元素或者范围不正确,就结束递归
return;
}else{
int lo = n, lt = lo + 1, gt = m;//lo表示基准的下标,lt表示基准后的数字得下标,gt表示数组最后一个数的下标
int pivot = arr[lo];//将每一个数组的第一个数作为基准
//遍历基准后的数,如果比基准小的,就直接交换两者的值,如果比基准大的数,那么就将这个数和数组最后面的数替换,再让基准和当前的数字进行比较
//如果相等,就将基准后一个数后移,即lt++,通过这个操作实现了基准左边都是小于基准的数,右边都是大于基准的数字
while(lt <= gt){
if(arr[lt] > pivot)//如果当前的数字大于基准,那么就将当前的数字和数组最后一个数字进行替换
swap(lt,gt--);
else if(arr[lt] < pivot)//如果当前的数字小于基准,那么就将当前的数字和基准进行替换
swap(lt++,lo++);
else
lt++;//如果当前的数字等于基准,那么就直接基准后一个数的下标后移
}
quickSort(n,lo - 1);//对左子数组进行快速排序
quickSort(gt + 1, m);//对右子数组进行快速排序
}
}
public static void swap(int n, int m){
int temp = arr[n];
arr[n] = arr[m];
arr[m] = temp;
}
}
7.基数排序
基数排序(RadixSort):主要思路是,将所有待比较数值(注意,必须是正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次稳定排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
图解:
方法一:通过数组来实现:
思路(个人的思路,有什么疑惑的地方请指教哈,哈哈哈):
①通过给出的数组arr,获得最大值max与最小值min,从而max – min的值有多少位数,进而可以知道要进行多少次递归(当然也可以通过循环来实现)
②新建一个数组,统计0 – 9 这10个数字分别出现了多少次
同时新建一个二维数组,其中有10行,arr.length列,来储存数组对应的数字,为什么是10行arr.length呢?因为每一行的下标对应的就是0 – 9这是个数字,列有arr.length是为了避免溢出,因为如果数字很多的情况呢,所以我觉得有arr.length列是避免溢出的情况。
③遍历数组arr,从而可以知道对应的0-9这10个数字出现的次数,并将对应的值赋给二维数组对应的行中去
④二维数组复制完毕之后,将遍历二维数组,将其值赋给原来的数组arr,然后重复上述操作max – min次,从而实现有序
考虑到数组中有负数的情况,那么要怎么解决呢?–通过将数组转换成含有非负数的数组。
将数组中的值都减去min这个值,从而将数组转化成含有非负数的数组,然后再将进行上述操作,不过注意的是,将二维数组的值赋给arr以后,arr并不是原来的数组了,因为转化成非负数的时候所有的数字都减去了min,所以还要遍历arr,将所有的数都加上min,才是要求的。
代码实现:
import java.util.Scanner;
public class RadixSort {
public static int[] arr = new int[10];
public static int k = 1;
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int max = 0;
int min = 0;
//输入数字,同时获得最大值、最小值
for(int i = 0; i<arr.length;i++) {
arr[i] = sc.nextInt();
if(max < arr[i])
max = arr[i];
if(min > arr[i])
min = arr[i];
}
//获取数组中最多有多少位数字 ---由于有了负数的存在,那么最大值的位数需要改变,因为下面的将数组化为整数的时候,最大值要改变,所以此时
//含有负数的数组化为非负整数的时候,最大值发生了改变,为max - min,如果数组是非负整数,因为下面的将数组化为整数的时候,最大值要改变
//所以此时的最大值还是max - min
int count = getCount(max - min);
getRadixSort(arr,count,min);//调用方法,进行基数排序
System.out.println("进行基数排序之后:");
for(int a : arr)
System.out.print(a+" ");
System.out.println();
}
public static int getCount(int max){
int count = 1;
max /= 10;
while(max != 0){
count++;
max /= 10;
}
return count;
}
public static void getRadixSort(int[] arr, int total,int min){
//利用递归来求解
if(total <= 0){
return;
}else{
int[] count = new int[10]; //新建一维数组,来统计每个数位出现的次数
int[][] array = new int[10][arr.length];//定义一个二维数组
for(int i = 0; i<arr.length; i++){
//获得一个数的个位、十位等数字
int digit = (arr[i] - min) / k % 10;//注意这里要是arr[i] - min,以防止负数的存在
array[digit][count[digit]]= (arr[i] - min);//将数组中的值赋值到有序数组中
count[digit] ++;
}
//将二位数组中的值赋给原来的数组
int m = 0;
for(int i = 0; i<10; i++){
if(count[i] != 0){
for(int j = 0; j<count[i]; j++){
arr[m++] = array[i][j];
}
}
}
//因为防止有负数,本来将数组的值都减去了min,所以要变成原来的数组,要将所有的数字都加上min才可以
for(int i = 0; i<arr.length; i++)
arr[i] += min;
k *= 10;
total--;
getRadixSort(arr,total,min);
}
//利用循环来实现
// while(total > 0){
// //统计每个数位出现的次数
// int[] count = new int[10];
// int[][] array = new int[10][arr.length];//定义一个有序数组
// for(int i = 0; i<arr.length; i++){
// int digit = (arr[i] - min) / k % 10;//获得最小值
// array[digit][count[digit]]= (arr[i] - min);//将数组中的值赋值到有序数组中
// count[digit] ++;
// }
// //将有序数组中的值赋给原来的数组
// int m = 0;
// for(int i = 0; i<10; i++){
// if(count[i] != 0){
// for(int j = 0; j<count[i]; j++){
// arr[m++] = array[i][j];
// }
// }
// }
// for(int i = 0; i<arr.length; i++)
// arr[i] += min;
// k *= 10;
// total--;
// }
}
}
通过链表来实现的代码:
/** * 通过链表来实现基数排序 * 思路: * 1、新建链表数组,从而表示有序数组 * 2、获得arr中的最大值、最小值,从而知道arr要转换成有序链表要循环max - min次 * 3、获得arr数组中的每一项中每一个位数出现的次数,然后将这个数放在链表数组对应的位置中 * 4、获得链表数组之后,遍历这个数组,然后将值赋给arr * 5、获得arr并不是要求的有序数组,需要遍历这个数组,将每一项都要加上min才是题目要求的 * 为什么要怎么做?因为要考虑负数存在的情况,因为有负数,所以进行基数排序,我们可以通过将 * 数组转换成最小值为0的数组(注意只是改变其值而已,而不是新建的数组),而要实现将数组转换成 * 含有非负数的,那么将每一项的值都减去min * 6、重复上述操作3、4、5,直至循环max - min次 */
package sort;
import java.util.Scanner;
public class RadixSort2 {
public static int[] arr = new int[10];
public static int k = 1;
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int max = 0, min = 0;
//输入数组,同时获得数组中的最大值、最小值
for(int i = 0; i<arr.length; i++) {
arr[i] = sc.nextInt();
if(max < arr[i])
max = arr[i];
if(min > arr[i])
min = arr[i];
}
int count = getCount(max - min);//获得数组arr转换成有序数组循环的次数
getRadixSort(count,min);
System.out.println("进行基数排序之后:");
for(int a: arr)
System.out.print(a+ " ");
System.out.println();
}
/** * 获得arr要转换成有序数组循环的次数 --- 也就是数组max - min的值有多少位数 * @param max * @return */
public static int getCount(int max){
int count = 1;
max /= 10;
while(max != 0){
count++;
max /= 10;
}
return count;
}
/** * 通过链表来实现基数排序 * @param total 表示要转换的次数 * @param min 表述数组中的最小值 */
public static void getRadixSort(int total, int min){
//通过循环来实现
while(total > 0){
LinkedList[] list = new LinkedList[10];//新建一个链表数组,长度为10,对应的是0-9这10个数字
for(int i = 0; i<list.length; i++)
list[i] = new LinkedList();//新建链表!!!注意这一步必须要有,否则没有办法初始化链表,也就没有实现其链表的相关操作
for(int i = 0; i<arr.length; i++){
int digit = ((arr[i] - min) / k) % 10;//将arr[i] - min以防数组中含有负数,从而将负数的数组转换成非负数的数组
list[digit].insert(arr[i] - min);//将对应的数插入到链表数组对应的位置 -- 减去min是避免了存在负数的情况
}
int m = 0;
//将链表数组中的值赋值给arr
for(int i = 0; i<list.length; i++){
if(!list[i].isEmpty()){
Node current = list[i].head.next;//因为定义的是带假节点的链表,所以链表真正的头节点就是head.next
//遍历链表,将值赋给数组
while(current != null){
arr[m++] = current.value;
current = current.next;
}
}
}
//这里和上面所说的一样的道理,都是避免负数的存在,转化非负数是将所有的值减去min,所以这里转化成原来的数组要将所有的值都加min才可以
for(int i = 0; i<arr.length; i++){
arr[i] += min;
}
k *= 10;
total--;
}
}
}
8.堆排序
思路:
①首先按照要求将数组构建成大顶堆或者小顶堆。所谓的大顶堆,就是根节点是数组的最大值,反之,小顶堆的根节点是数组的最小值。
②然后在构建完毕之后,将根节点和数组的最后一个节点进行交换,交换完毕之后,数组的个数要减1,从而在之后的操作中不在涉及到最后的数,保证了最大或最小的数在最后。
③然后通过递归,重复上面的操作①②,直到数组的个数为1结束递归。
public class SortApp {
public static int[] arr = new int[10];//定义一个数组
public static int count = arr.length;//元素的个数
public static void main(String[] args) {
for(int i = 0; i<arr.length; i++) {
arr[i] = (int)(Math.random() * 100);//随机产生10个随机数
}
System.out.print("未进行堆排序之前:");
display();
for(int i = 1; i<arr.length;i++)
tickUp(i);//构建大顶堆
sort();//进行堆排序
System.out.print("进行堆排序之后:");
display();//输出
}
public static void display() {
for(int a: arr)
System.out.print(a +" ");
System.out.println();
}
public static void sort() {
if(count == 1)//如果只有一个元素,那么结束递归
return;
swap(0,count - 1);//将根节点和最后一个节点进行交换,然后将元素个数减1,从而将最后一个数固定
count --;
/* 不知道细心的小伙伴有没有发现如果这样写的话会存在一个bug, 那就是如果一开始数组就只有一个元素,那么这时候经过swap之后 count的值变成了0,此时不等于1,如果这样写的话,那么就会 继续递归,此时就会发生报错,因为-1发生越界了,所以 需要将这一步代码放到swap之前,或者判断是count <= 1,但明显放到 前面会好些,因为不需要进行swap这一步操作 if(count == 1)//如果只有一个元素,那么结束递归 return; */
tickDown(0);//由上面可以知道,根节点和最后一个节点交换了,那么此时根节点不一定是最大的,那么需要和它的子节点进行比较,从而再次构建大顶堆
sort();//构造完毕之后,再次递归,进行堆排序
}
public static void swap(int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;//将根节点和最后一个节点进行交换
}
/** * 向下筛选,从而再次构建大顶堆 * @param index */
public static void tickDown(int index) {
int larger;
int temp = arr[index];//将父节点赋给一个临时变量,然后和它的子节点进行比较,如果大于,就说明他的位置是对的,否则进行交换
while(index < count / 2) {
//循环条件必须是index < count / 2,否则再计算左右子节点的时候发生报错
int leftChild = index * 2 + 1;
int rightChild = index * 2 + 2;
if(rightChild < count && arr[leftChild] < arr[rightChild]) {
//如果含有右子节点,而且左右子节点中右子节点最大,那么larger = rightChild
larger = rightChild;
}else {
//这里有两种情况,首先如果没有右子节点,那么larger 就是leftChild,其次如果有右子节点,而且左子节点大于右子节点,那么larger = leftChild
larger = leftChild;
}
if(arr[larger] <= temp) {
//如果父节点大于等于子节点,那么这个位置就是当前节点的位置,退出循环,注意不可以在这里写arr[index] = temp,然后在结束循环的时候不写这一步了
//因为有可能一直是arr[larger]一直大于temp,直到index = count / 2,退出循环,从而没有办法插入temp,导致数组中有两个相同的数
break;
}else {
arr[index] = arr[larger];//如果父节点小于子节点,那么子节点替换到其父节点的位置,父节点替换到其子节点的位置
index = larger;
}
arr[index] = temp;//这里有两种情况,首先第一种是index = count / 2,从而结束循环,第二种是arr[larger] <= temp,从而结束循环
}
}
/** * 通过向上筛选进行将节点插入到堆中 * @param index */
public static void tickUp(int index) {
int temp = arr[index];
int parent = (index - 1) / 2;//获取其父节点
while(index > 0 && arr[parent] < temp) {
//当父节点小于子节点的时候,将其进行交换
arr[index] = arr[parent];
index = parent;
parent = (parent - 1) / 2;
}
arr[index] = temp;
}
}
通过参考其他大佬的代码,将只保留tickDown方法:
public class Sorting {
public static int[] arr = new int[10];
public static int count = arr.length;//定义数组的个数
public static void main(String[] args) {
for(int i = 0; i<count; i++)
arr[i] = (int)(Math.random()*100);
System.out.println("没有进行堆排序之前:");
display();
heapify();//首先将一个无序数组构建成一个大顶堆
sort();//进行堆排序
System.out.println("进行堆排序之后:");
display();
}
/** * 遍历数组 */
public static void display() {
for(int a:arr)
System.out.print(a+" ");
System.out.println();
}
public static void sort() {
if(count == 1)//如果只有一个元素,那么结束递归
return;
swap(0,count - 1);//将根节点和最后一个节点进行交换,然后将元素个数减1,从而将最后一个数固定
count --;
/* 不知道细心的小伙伴有没有发现如果这样写的话会存在一个bug, 那就是如果一开始数组就只有一个元素,那么这时候经过swap之后 count的值变成了0,此时不等于1,如果这样写的话,那么就会 继续递归,此时就会发生报错,因为-1发生越界了,所以 需要将这一步代码放到swap之前,或者判断是count <= 1,但明显放到 前面会好些,因为不需要进行swap这一步操作 if(count == 1)//如果只有一个元素,那么结束递归 return; */
adjustHeap(0);//再次构建最大堆
sort();//递归,从而再次将根节点和最后一个节点交换
}
public static void swap(int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void heapify() {
//从最后一个非叶子节点开始遍历,将其和它的子节点进行比较,如果自身小于它的子节点,那么就进行交换,从而构建大顶堆
for(int i = arr.length / 2 - 1; i >= 0; i --)
adjustHeap(i);
}
public static void adjustHeap(int index) {
int temp = arr[index];//将父节点的值赋给临时变量,将其和它的子节点进行比较
int larger;//标记左右子节点中哪一个子节点的值更大,然后将其和temp进行比较,如果temp大,那么这个父节点的位置是正确的,否则进行交换
while(index < count / 2) {
int leftChild = index * 2 + 1;
int rightChild = index * 2 + 2;
if(rightChild < count && arr[leftChild] < arr[rightChild]) {
//这里必须要有rightChild,因为考虑到当index 为count / 2 - 1时,只有一个左子节点
//当存在右子节点时,那么将左右子节点进行比较,从而获得两者中的最大值
larger = rightChild;
}else {
//当没有右子节点时,那么larger 为leftChild,当有右子节点,而且两个节点中,左子节点最大,那么larger 为leftChild
larger = leftChild;
}
if(arr[larger] <= temp) {
//当父节点的值比其左右子节点都大,那么就退出循环
break;
}else {
//当父节点的值小于其子节点的值,那么进行交换
arr[index] = arr[larger];
index = larger;
}
}
arr[index] = temp;
}
}
我是第一次写博客的,有写的不好的地方,还请大家原谅啊,哈哈哈,当然也请大家指正,下次一定会改正的哦。