前言
- 此篇来介绍常用的排序算法Java版实现,JavaScript版请戳这里。其实网上的介绍讲解汗牛充栋,并且有很多优秀的文章,写此篇的目的是为了加深下自己的印象,还有融入点自己的想法。当笔记使啦~
- 注:排序元素顺序以从小到大排序为主;
冒泡排序
- 冒泡排序属于相对较简单的排序,过程如下:每次遍历【数组】一遍,确定出最大的那个数放在数组的最右边,当然确定的这个数就不在下次遍历【数组】内啦,依次确定【数组】中的每一个元素。(从左->右:小->大),代码实现如下
public class BubbleSort {
public static void main(String[] args) {
//定义无序数组
int[] arr = {3,9,6,-4,12,5,1};
bobbleSort(arr);
}
public static void bobbleSort(int[] arr) {
for(int i=0;i<arr.length;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;
}
}
}
//打印结果
//System.out.println(Arrays.toString(arr));
}
}
- 性能:十万数据在我机器上大约需要10秒。
选择排序
- 选择排序相对于冒泡排序性能上有所提高,并且很可观。过程大致是这样:每次在循环区间找出最小/最大的数的索引【选择过程】,然后将此数放在指定位置【队头/队尾】,记住找到的是索引哦~!
- 实现如下
public class SelectSort {
public static void main(String[] args) {
int[] arr = {3,9,6,-4,12,5,1};
selectSort(arr);
}
public static void selectSort(int[] arr) {
for(int i = 0;i<arr.length-1;i++) { //控制选择区间大小
//定义中间变量记录最小值的索引
int minIndex = i;
for(int j=i+1;j<arr.length;j++) { //在选择区间中选择最小的数并记录其index
if(arr[j]<arr[minIndex]) {
minIndex = j;
}
}
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
//System.out.println(Arrays.toString(arr));
}
}
- 性能上来看:十万数据在我机器上大约仅需要两秒钟~~
插入排序
- 插入排序性能上略优于冒泡,它的过程是这样的,面对一段需要排序的数组,我们截取一段默认为它是有序的【从索引为0的元素开始,长度为1】,然后依次在剩下的无序数组中选取元素填充到我们的有序数组中【当然需要在适当的位置放入这个元素】,最后,有序数组的长度等于原数组的长度,这样,便实现了排序的效果。
- 插入排序在寻找元素的适当位置时,寻找的是索引。
- 插入的位置可以是“有序数组”的头部,内部,或是尾部。
- 实现如下:
public class InsertSort {
public static void main(String[] args) {
//定义排序数组
int[] arr = new int[] {10,9,6,-4,12,5,1};
insertSort(arr);
}
public static void insertSort(int[] arr) {
for(int i=0;i<arr.length-1;i++) {
//定义插入索引
int j = i+1;
//定义中间变量
int temp = arr[j];
while (j >= 1 && arr[j-1]>temp) {
arr[j] = arr[j-1];
j--;
}
arr[j]=temp;
}
System.out.println(Arrays.toString(arr));
}
}
- 插入排序的性能:十万数据在我机器上大约需要3秒钟。
希尔排序
- 希尔排序相对来说是比较难理解的一种排序算法,网上有很多单独讲解的优质文章,在这里提供几个关键点仅助理解记忆。
- 铺垫:希尔排序是对插入排序的优化版排序算法【了解希尔排序之前建议掌握插入排序的思想与操作】。1、插入排序对基本有序的数组性能是非常高的,希尔排序首先是小构造基本有序的数组。2、插入排序元素只能移动一位,希尔排序可针对相距较远的元素进行交换。
- 过程:希尔排序每次将数组的length/2作为步长,【步长的作用:对元素进行分组】对每组元素进行插入排序。
- 注意:1、分组的规则是跳跃式的,根据步长跳跃。2、代码实现时,每次循环对多组同时进行插入排序,而不是一组插入排序进行完再进行第二组。3、分组是逻辑上的分组,不是实际将数组拆分开来。
- 过程演示:
- 相关解释:粉色数组为目标数组/绿色数组是逻辑上的分组/浅红色数组是逻辑数组上经过插入排序后的结果。
- 代码实现:
- 交换方式上希尔排序/移位方式上的希尔排序:
public class ShellSort {
public static void main(String[] args) {
int[] arr = {8,9,7,2,4,1,0,5,3,6};
shellSort(arr);
shellSortT(arr);
}
private static void shellSort(int[] arr) {
// 定义中间变量用于交换
int temp = 0;
int count = 0;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) { // 用于在步长的基础上遍历元素
for (int j = i - gap; j >= 0; j -= gap) { // 外层加,内层减
if (arr[j] > arr[j + gap]) { // 控制排序
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
}
//对交换式的shell排序的优化->移位式
private static void shellSortT(int[] arr) {
//增量gap,逐步缩小增量
for(int gap = arr.length/2;gap > 0;gap /= 2) {
//从第gap个元素,逐个对其所在的组进行直接插入排序
for(int i = gap; i < arr.length; i++) {
//定义需要插入的索引
int j = i;
int temp = arr[j];
while(j - gap >= 0 && temp < arr[j-gap]) {
//移位
arr[j] = arr[j-gap];
j -= gap;
}
//当退出while循环后,给temp找到插入的位置
arr[j] = temp;
}
}
//System.out.println(Arrays.toString(arr));
}
}
归并排序
- 归并排序采用分治思想,先分后治,过程是这样的:我们逻辑上认为可以将数组分为很多个有序的子数组,然后我们依次两两将其合并,合并后依然使其有序,我们即可得到一个合并后的大数组,这时,此数组就是有序的。
- 合并过程:遍历两个有序数组,按顺序分别挑出两个数组中的元素,其中元素较小的“入库”,随后再从挑出较小元素的数组中取出一个元素,与之比较,小者入库。依次往复。。。最终处理完所有的元素,将库中的元素拷贝到元素中中。
- 实现代码如下:
public class MergeSort {
public static void main(String[] args) {
int arr[] = {8,4,5,7,1,3,6,2};
int temp[] = new int[arr.length];
mergeSort(arr, 0, arr.length-1, temp);
System.out.println("归并排序后的结果:"+Arrays.toString(arr));
}
//分+合方法
private 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, mid, right, temp);
}
}
/**
* @param arr 需要排序的目标数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 中间临时数组
*
* */
private static void merge(int[] arr,int left,int mid,int right,int[] temp) {
int i = left; //初始化i,左边有序序列的初始索引
int j = mid + 1; //初始化j,右边有序序列的初始索引
int t = 0; //中间数组的游标
//(一)
//先把左右两边(有序)数组按照规则填充到temp中
//直到左右两边的有序序列,有一边处理完为止
while(i <= mid && j<= right) {
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到temp数组
//然后t++,i++
if(arr[i] <= arr[j]) {
temp[t] = arr[i];
t+=1;
i+=1;
}else { //反之,将右边元素填充到temp数组
temp[t] = arr[j];
t+=1;
j+=1;
}
}
//(二)
//把所有剩余元素填充到temp
//左
while(i<= mid) {
temp[t] = arr[i];
t+=1;
i+=1;
}
//右
while(j<= right) {
temp[t] = arr[j];
t+=1;
j+=1;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
//tempLeft = 0,right = 1;tempLeft = 2,right = 3;
//最后tempLeft = 0,right = 7;
t = 0;
int tempLeft = left;
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
快速排序
-
我的数据结构老师口中的快速排序如下:
-
而我印象中的过程是这样滴:在数组中找到一个标量,分别在数组的两头构造两个指针,使其向中间扫描,当然,两个指针相遇后就不再向下进行了。左指针遇到大于标量的时候停下,右指针遇到小于标量的时候停下。然后两指针指向的数字相互交换。最后将标量与两指针相遇的地方交换。
-
实现如下:
public class QuickSort {
/**
* 测试
* @param args
*/
public static void main(String[] args) {
//49,33,61,82,75,12,25,58,29
int[] num = {49,33,61,82,75,12,25,58,29};
QuickSort(num,0,num.length-1);
}
/**
* 快速排序
* @param num 排序的数组
* @param left 数组的前针
* @param right 数组后针
*/
private static void QuickSort(int[] num, int left, int right) {
//如果left等于right,即数组只有一个元素,直接返回
if(left>=right) {
return;
}
//设置最左边的元素为基准值
int key=num[left];
//数组中比key小的放在左边,比key大的放在右边,key值下标为i
int i=left;
int j=right;
while(i<j){
//j向左移,直到遇到比key小的值
while(num[j]>=key && i<j){
j--;
}
//i向右移,直到遇到比key大的值
while(num[i]<=key && i<j){
i++;
}
//i和j指向的元素交换
if(i<j){
int temp=num[i];
num[i]=num[j];
num[j]=temp;
}
}
num[left]=num[i];
num[i]=key;
QuickSort(num,left,i-1);
QuickSort(num,i+1,right);
}
}
基数排序
- 基数排序详解地址 以前用JavaScript写过一次,并且个人感觉还挺详细,这里直接上代码啦:
public class RadixSort {
public static void main(String[] args) {
//测试
int arr[] = {23,6,189,45,9,287,56,1,798,34,65,652,5};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
private static void radixSort(int arr[]) {
//创建一个栈数组
List<Queue<Integer>> list = Arrays.asList(new Queue[10]);
//取出最大的数
int max = 0;
for(int i=0;i<arr.length;i++) {
if(arr[i]>max) {
max = arr[i];
}
}
//取位数
int length = String.valueOf(max).length();
//System.out.println(length);
//通过位数进行遍历
for(int i=0,n=1;i<length;i++,n=n*10) {
//填入桶中
for(int k = 0;k<arr.length;k++) {
//取余数【10进制】
int ys = arr[k]/n%10;
if(list.get(ys) == null) {
list.set(ys, new LinkedList<Integer>());
}
list.get(ys).add(arr[k]);
}
//将数字取出
int index = 0;
for(int j=0;j<list.size();j++) {
if(list.get(j)!=null) {
while(!list.get(j).isEmpty()) {
arr[index] = list.get(j).poll();
index++;
}
}
}
}
}
}
堆排序
- 堆排序利用了二叉树的性质,首先要构建一个大顶堆或者小顶堆,将堆顶元素取出,将剩下元素重新构建顶堆结构。这样往复循环,便可得到一个有序数组。
- 实现代码如下
public class HeapSort {
public static void main(String[] args) {
int[] arr = {3,4,8,6,9,11,7,1,14,12};
System.out.println("排序前"+Arrays.toString(arr));
heapSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
//编写一个堆排序算法
public static void heapSort(int[] arr){
int temp = 0;
System.out.println("堆排序");
//将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
for(int i=arr.length/2-1;i>=0;i--){
adjustHeap(arr,i,arr.length);
}
/**
* 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
* 重新调整结构,使其满足定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤
* 直到整个序列有序
* */
for(int j=arr.length-1;j>0;j--){
//交换
temp=arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr,0,j);
}
}
/**
* @param arr 待调整的数组
* @param i 表示非叶子节点在数组中的索引
* @param length 表示对多少个元素继续调整,length是在逐渐减少
* */
public static void adjustHeap(int arr[],int i,int length){
//先取出当前节点的值
int temp = arr[i];
//开始调整 k*2+1 是i的左子节点
for(int k=2*i+1;k<length;k=2*k+1){
if(k+1 < length && arr[k+1]>arr[k]){ //说明右子节点的值大于左子节点的值
k++;
}
if(arr[k] > temp){ //交换子节点与父节点的位置
arr[i] = arr[k];
i=k;
} else {
break;
}
}
//当for循环结束后,我们已经将以 i 为父节点的树最大值,放在了最顶(局部)
arr[i] = temp; //将temp值放到调整后的位置
}
}
结语
- 完结撒花~