数据结构中的八大排序算法
1、冒泡排序
1.1、实现的思路:
通过对待排序序列从前向后(从下标较小的元素开始)依次比较相邻的元素,若发现逆序则交换(即如果我们想从小到大排,发现前面的大于后面的,就是逆序,反之亦然)。使值较大的元素逐渐从前移向后部。(这样得到的数组是从小到大的)
public class BubbleSorting {
public static void main(String[] args) {
int[] array1 = {3, 2, 5, 4, 1};
System.out.println("最终排序好的数组:\n" + Arrays.toString(bubbleSorting1(array1)));
}
//普通的冒泡排序
public static int[] bubbleSorting1(int[] array) {
//外循环多少趟,每一趟将得到的一个最大的值放到下标最大的地方
for (int i = 0; i < array.length - 1; i++) {
//内循环多少次,每一次将相邻的两个元素中较大的一个放到下标大的位置
for (int j = 0; j < array.length - 1 - i; j++) {
//如果相邻两个元素中前面的元素大于后面的元素,就将其交换位置
if (array[j] > array[j + 1]) {
int num = array[j];
array[j] = array[j + 1];
array[j + 1] = num;
}
}
System.out.println("第" + (i + 1) + "次排序后的数组");
System.out.println(Arrays.toString(array));
}
return array;
}
}
1.2、优化后的思路:
因为排序的过程之中,各个元素是不断接近自己的位置,如果一趟比较下来没有进行过元素交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换,从而减少不必要的比较。(这样得到的数组是从小到大的)
public class BubbleSorting {
public static void main(String[] args) {
int[] array2 = {3, 9, 2, 10, 11};
System.out.println("最终排序好的数组:\n" + Arrays.toString(bubbleSorting2(array2)));
}
//优化后的冒泡排序
public static int[] bubbleSorting2(int[] array) {
//设置一个标识符,从而可以判断在一趟排序中,是否进行过元素的交换
boolean flag = false;
int num = 0;
//外循环多少趟,每一趟将得到的一个最大的值放到下标最大的地方
for (int i = 0; i < array.length - 1; i++) {
//内循环多少次,每一次将相邻的两个元素中较大的一个放到下标大的位置
for (int j = 0; j < array.length - 1 - i; j++) {
//如果相邻两个元素中前面的元素大于后面的元素,就将其交换位置
if (array[j] > array[j + 1]) {
num = array[j];
array[j] = array[j + 1];
array[j + 1] = num;
flag = true;
}
}
System.out.println("第" + i + "次排序后的数组");
System.out.println(Arrays.toString(array));
if (!flag) {
break;
} else {
//重置flag,方便下一趟继续判断
flag = false;
}
}
return array;
}
}
1.3、小结:
1、一共进行array.length - 1轮外循环
2、每一轮排序的次数在逐渐的减少,所以内循环为array.length - 1 - i
3、如果我们发现某一趟排序中,没有发生一次交换,可以提前结束冒泡排序
2、选择排序
2.1、实现的思路:
每一次都选出该数组最大的值,然后与该数组的最后一个元素进行交换,然后将数组下标往前移一位,因为当前数组的最大值已经被移到了最后一位,所以这个元素可以不用参加之后的排序,依次执行上述的步骤,直至数组只剩下最后一个元素,循环结束。(这样得到的数组是从小到大的)
第一次从array[0]~array[n-1]选出最大值,与array[n-1]交换
第二次从array[0]~array[n-2]选出最大值,与array[n-2]交换
……
第i次从array[0]~array[n-i]选出最大值,与array[n-i]交换
……
第n-1次从array[0]~array[1]选出最大值,与array[1]交换
public class SelectSorting {
public static void main(String[] args) {
int[] array = {3, 5, 4, 7, 9};
System.out.println("最终排序好的数组:\n" + Arrays.toString(selectSorting(array)));
}
public static int[] selectSorting(int[] array) {
//定义一个值,来接收每一趟得到的最大数的下标
int num = 0;
int temp = 0;
//外循环多少趟,每一趟将得到的一个最大的值放到下标最大的地方
for (int i = 0; i < array.length - 1; i++) {
//内循环多少次,每一次将这一轮所有的元素与array[num]进行比较,如果大于array[num],就将下标赋值给num
for (int j = 0; j < array.length - i; j++) {
if (array[num] < array[j]) {
num = j;
}
}
temp = array[array.length - (i + 1)];
array[array.length - (i + 1)] = array[num];
array[num] = temp;
num = 0;
System.out.println("第" + (i + 1) + "次排序后的数组");
System.out.println(Arrays.toString(array));
}
return array;
}
}
2.2、小结:
1、选择排序一共有array.length - 1轮排序
2、每一轮排序需要比较array.length - i次
3、每轮排序,选出一个最大的值
4、将这个最大的值与这趟数组中下标最大的元素交换
3、插入排序
3.1、实现的思路:
把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,排序过程中每次从无序表中取出第一个元素,把他的值依次与有序表元素的值进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
如下图所示
public class InsertionSorting {
public static void main(String[] args) {
int[] array = {9, 4, 7, 3, 8};
System.out.println("最终排序好的数组:\n" + Arrays.toString(insertionSorting(array)));
}
public static int[] insertionSorting(int[] array) {
//定义一个接收临时数据的变量
int temp;
//因为无序表的元素是去除掉第一个元素的,所以要 -1
for (int i = 0; i < array.length - 1; i++) {
//因为无序表是从第二个元素开始的,所以要 +1
temp = array[i + 1];
//将这个元素的下标保存下来,并作为指示有序表中被比较元素的下标
int j = i;
//这个表示还没有找到这个元素该放的位置
while (j > -1 && temp <= array[j]) {
//就将有序表中的这个被比较的元素后移一位
array[j + 1] = array[j];
//下标 --,表示被比较的元素前移一位
j--;
}
//表示已经找到了该插入的位置
array[j + 1] = temp;
System.out.println("第" + (i + 1) + "次排序后的数组");
System.out.println(Arrays.toString(array));
}
return array;
}
}
3.2、小结:
1、首先将这一个传入的数组看成是两个部分,第一个部分为有序表,里面包含传入的数组的第一个元素
第二部分为无序表,里面包含的元素为从第二个元素开始到数组的最后一个元素
2、将无序表里面的第一个元素用一个临时变量存起来
3、将这个元素依次与有序表的从后往前进行比较,若小于有序表里面的元素且下标还大于 -1(表示没有越界),
则表示没有找到该元素应该插入的位置,则将这个被比较的元素后移一位,然后重复以上动作
4、若是不满足这个条件,则表示已经找到这个元素该插入的位置,则将这个元素插入到这个被比较元素的后一位
5、然后一直循环就好了
4、希尔排序
4.1、定义:
其实本质也是插入排序,不过是经过改进之后的。
希尔排序又分为交换法与移位法
交换法:分完组之后两两比较,效率较低(这个本质其实是采用的冒泡排序的方式进行的)
移位法:分完组之后从第二个位置开始,直接找到他所应该在的位置,然后直接插入到该位置,效率较高(其实本质才是使用的插入排序的方法进行的)
4.2、实现的思路:
把记录按下标的一定增量分组,对每组使用直接插入排序算法进行排序随着增量的减少,每组包含的关键词越来越多,当增量减少到 1时,整个文件被分为一组,算法便结束。
如下图所示
public class ShellSorting {
public static void main(String[] args) {
int[] array1 = {9, 4, 7, 3, 8, 6};
System.out.println("最终排序好的数组:\n" + Arrays.toString(shellSorting1(array1)));
System.out.println("------------------------------");
int[] array2 = {9, 4, 7, 3, 8, 6};
System.out.println("最终排序好的数组:\n" + Arrays.toString(shellSorting2(array2)));
}
//采用交换法
public static int[] shellSorting1(int[] array) {
//定义一个临时变量,用于存储数据
int temp = 0;
int count = 0;
//i为增量,也就是这个数组被分为几组,之后每一次都将这个增量除以 2
for (int i = array.length / 2; i > 0; i /= 2) {
//一共要对多少组进行插入排序
for (int j = i; j < array.length; j++) {
//遍历每一组里面所有的元素,并对其进行冒泡排序
for (int k = j - i; k >= 0; k -= i) {
//如果前面的元素大于后面的元素,就交换其位置
if (array[k] > array[k + i]) {
temp = array[k];
array[k] = array[k + i];
array[k + i] = temp;
}
}
}
System.out.println("第" + ++count + "次排序后的数组");
System.out.println(Arrays.toString(array));
}
return array;
}
//采用移动法
public static int[] shellSorting2(int[] array) {
//定义一个临时变量,用于存储数据
int temp = 0;
int num = 0;
int count = 0;
//i为增量,也就是这个数组被分为几组,之后每一次都将这个增量除以 2
for (int i = array.length / 2; i > 0; i /= 2) {
//从第一组的第二个元素开始,逐个对其所在的组进行插入排序,共进行 i组
for (int j = i; j < array.length; j++) {
//将要插入的元素的下标保存
num = j;
//将要插入的元素保存
temp = array[num];
//只有当要插入的元素小于前面的元素时才需要进行移动(因为我要排完的顺序是从小到大)
if (array[num] < array[num - i]) {
//要保证被插入的位置的下标不能越界且要插入的元素小于被插入的元素则表示位置没被找到,继续向前找
while (num - i >= 0 && temp < array[num - i]) {
//被插入的元素后移
array[num] = array[num - i];
num -= i;
}
//表示要插入的位置被找到了
array[num] = temp;
}
}
System.out.println("第" + ++count + "次排序后的数组");
System.out.println(Arrays.toString(array));
}
return array;
}
}
4.3、小结:
1、整个算法最核心的就是分组,分组的规则是:
每一次都将传入数组.length/2且结果向下取整,如:除完的结果为2.5,那么结果就为2
2、分完组之后,对每一组分别使用插入排序,即每一组里面的元素就会变得有序,而整个数组也相对有序
3、重复以上的过程,直至分组的结果为 1,这个时候只需要将整个数组分为一组,在进行插入排序即可
5、快速排序
5.1、定义:
本质也是对冒泡排序的一种改进。
5.2、实现的思路:
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都要比另一部分的所有数据都要小,然后在对这两个部分的数据分别进行快速排序,整个排序的过程可以递归进行,以此达到整个数组变为有序数列。
如下图所示
public class QuickSorting {
public static void main(String[] args) {
int[] array1 = {-9, 78, 0, 23, -567, 70};
System.out.println("最终排序好的数组:\n" + Arrays.toString(quickSorting1(array1, 0, array1.length - 1)));
System.out.println("---------------------------");
int[] array2 = {-9, 78, 0, 23, -567, 70};
System.out.println("最终排序好的数组:\n" + Arrays.toString(quickSorting2(array2, 0, array2.length - 1)));
}
//以中间的数为轴
public static int[] quickSorting1(int[] array, int lefts, int rights) {
int left = lefts;
int right = rights;
//中轴值
int center = array[(left + right) / 2];
//保存临时变量
int temp = 0;
//while循环的目的是将比center大的放右边,比center小的放左边
while (left < right) {
//在center的左边一直寻找,当找到大于等于center的值时退出
while (array[left] < center) {
left += 1;
}
//在center的右边一直寻找,当找到小于等于center的值时退出
while (array[right] > center) {
right -= 1;
}
//如果 left >= right说明 center的左右的值已经按照
//center左边的值都小于center,center右边的值都大于center
if (left >= right) {
break;
}
//交换位置
temp = array[left];
array[left] = array[right];
array[right] = temp;
//如果交换后,发现 array[left] == center的值,将 left--,前移
if (array[left] == center) {
right -= 1;
}
//如果交换后,发现 array[right] == center的值,将 right++,后移
if (array[right] == center) {
left += 1;
}
System.out.println(Arrays.toString(array));
}
//帮助新的需要排序的数组的左右两侧在合适的位置
if (left == right) {
left += 1;
right -= 1;
}
//向左递归
if (lefts < right) {
quickSorting1(array, lefts, right);
}
//向右递归
if (rights > left) {
quickSorting1(array, left, rights);
}
return array;
}
//以最左边的数为轴
public static int[] quickSorting2(int[] array, int lefts, int rights) {
int left = lefts;
int right = rights;
//中轴
int center = array[lefts];
//while循环的目的是将比center大的放右边,比center小的放左边
while (left < right) {
/**
* 注意:
* 一定要先查右边!!!
* 因为,我们是把左边的第一个数作为了中轴,这个时候如果我们继续从左边开始查找,那么直接进行一次
* if(left < right){array[right] = array[left]; right--;}操作,会将第一个数放置最后,
* 而最后一个数则被覆盖,以至于排序后的数组是错误的
*/
//在center的右边一直寻找,当找到小于等于center的值时退出
while (left < right && array[right] >= center) right--;
//表示找到了一个比center小的数,将他放到左边去
if (left < right) {
array[left] = array[right];
left++;
}
//在center的左边一直寻找,当找到大于等于center的值时退出
while (left < right && array[left] < center) left++;
//表示找到了一个比center大的数,将他放到右边去
if (left < right) {
array[right] = array[left];
right--;
}
}
array[left] = center;
//向左递归
if (lefts < left) quickSorting2(array, lefts, left - 1);
//向右递归
if (rights > left) quickSorting2(array, right + 1, rights);
return array;
}
}
5.3、小结:
1、注意:
使用最左边的数为中轴的时候
一定要先递归查询右边!!!
因为,我们是把左边的第一个数作为了中轴,这个时候如果我们继续从左边开始查找,那么直接进行一次
if(left < right){
array[right] = array[left]; right--;
}
操作,这样的话会将第一个数放置最后,
而最后一个数则被覆盖,以至于排序后的数组是错误的
反之亦然,使用最右边的数作为中轴也不能先从右边开始查找
但是如果是使用中间的数作为中轴,那么就不会出现这样的问题了
2、递归的时候要注意传入的左右两边的索引要进行改变,不能每一次都传入同样的值
3、每一次递归完之后,中轴的值也会相应的进行改变
6、归并排序
6.1、实现的思路:
该算法采用的是分治法实现的。
分治法:先将问题分为一些小的问题然后递归求解,而治的阶段则是将分的阶段得到的各个答案“修补”在一块,即分而治之
如下图所示
public class MergeSorting {
public static void main(String[] args) {
int[] array = {-9, 78, 0, 23, -567, 70};
//归并排序需要一个额外的空间
int[] temp = new int[array.length];
System.out.println("最终排序好的数组:\n" + Arrays.toString(mergeSorting(array, 0, array.length - 1, temp)));
}
/**
* 分离数组的方法
*
* @param array 排序的原数组
* @param left 数组的左下标
* @param right 数组的右下标
* @param temp 保存中转的临时数组
* @return 返回已经排序好的数组
*/
public static int[] mergeSorting(int[] array, int left, int right, int[] temp) {
//只要左下标小于右下标,表示还能够继续进行分解
if (left < right) {
//定义一个中间索引,用于区分分解后的左右数列
int middle = (left + right) / 2;
//向左递归进行分解
mergeSorting(array, left, middle, temp);
//向右递归进行分解
mergeSorting(array, middle + 1, right, temp);
merge(array, left, middle, right, temp);
}
return array;
}
/**
* 合并数组的方法
*
* @param array 排序的原数组
* @param left 左边有序数列的初始下标
* @param middle 中间索引,用于区分左右两个数列
* @param right 右边有序数列的初始下标
* @param temp 保存中转的临时数组
*/
public static void merge(int[] array, int left, int middle, int right, int[] temp) {
//初始化左边有序数组的初始索引
int i = left;
//初始化右边有序数列的初始索引
int j = middle + 1;
//初始化临时数组temp的当前索引
int t = 0;
/*
第一步:
先把左右两边的有序数列按照规则依次的填入temp临时数组中,直到左右两边的有序数列有一边已经全部处理完毕
*/
//首先要保证i、j不会越界
while (i <= middle && j <= right) {
if (array[i] < array[j]) {
//如果左边有序数列的值小于右边有序数列的值,则将左边的数据放到temp之中
temp[t] = array[i];
i++;
t++;
} else {
//否则的话表示左边有序数列的值大于右边有序数列的值,则将右边的数据放到temp之中
temp[t] = array[j];
j++;
t++;
}
}
/*
第二步:
把另外一边的有序数列的剩余数据依次的填入temp数组之中
*/
//表示左边的有序数列还有数据没有放到temp中
while (i <= middle) {
temp[t] = array[i];
i++;
t++;
}
//表示右边的有序数列还有数据没有放到temp中
while (j <= right) {
temp[t] = array[j];
j++;
t++;
}
/*
第三步:
将temp数组中的元素复制到array之中
注意:
每一次复制的数量是不一样的
*/
//首先要将temp的当前索引归零
t = 0;
//因为array数组的left不能改变,所以定义一个tempLeft,这样方便接收从temp里面复制过来的值
int tempLeft = left;
//如果tempLeft没有越界,那么就可以将temp里面的值给复制过来
while (tempLeft <= right) {
array[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
}
6.2、小结:
简单来说就是:
将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。
7、基数排序
7.1、实现的思路:
将所有待比较的数值统一为同样的数位长度,数位较短的数前面补零,然后从最低为开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变为一个有序数列了。
如下图所示:
public class RadixSorting {
public static void main(String[] args) {
int[] array = {9, 78, 0, 23, 567, 70};
System.out.println("最终排序好的数组:\n" + Arrays.toString(radixSorting(array)));
}
/**
* 基数排序
*
* @param array 传入的需要处理的数组
* @return 返回的已经排序好的数组
*/
public static int[] radixSorting(int[] array) {
/*
第一步:
首先要得到这个数列的最大的数有几位,这样才可以直到我们要循环到哪一个程度
第二步:
首先需要定义一个二维数组,这个二维数组包含了十个一维数组,因为每一个数字都是由0~9组成的
每一个一维数组表示我们用来装数据的“桶”,且为了防止数据溢出,所以将一维数组的长度定义为array.length
第三部:
为了记录每一个“桶”之中存放了多少个数据,所以我们需要定义一个一维数组来保存每一个“桶”里面有多少个数据,这个一维数组的长度为10
第四步:
就是一次将array里面的数据按照规则依次放入“桶”之中,如此反复,最后得到的数列就是有序的了
*/
//假设max为该数组的最大值
int max = array[0];
for (int i = 0; i < array.length; i++) {
if (max < array[i]) {
max = array[i];
}
}
//设maxLength为最大数的长度
int maxLength = (max + "").length();
//用于存放十个“桶”
int[][] bucket = new int[10][array.length];
//用于表示每个“桶”之中有多少个元素
int[] bucketElementCount = new int[10];
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//对每个元素的对应位进行处理,第一次为个位,第二次为十位……
for (int j = 0; j < array.length; j++) {
//首先取出每个元素的对应位的值
int digitElement = array[j] / n % 10;
//再将其放入到对应的桶之中
bucket[digitElement][bucketElementCount[digitElement]] = array[j];
//将指向该“桶”的索引后移一位,表示该“桶”多了一个元素
bucketElementCount[digitElement]++;
}
//按照“桶”里面的顺序,依次取出数据,将其放入到原数组之中,定义一个变量记录当前的下标
int index = 0;
//遍历每一个“桶”,并将桶中的数据,放入到原数组之中
for (int j = 0; j < bucketElementCount.length; j++) {
//如果“桶”中有数据,才可以放入到原数组之中,也就是不能为0
if (bucketElementCount[j] != 0) {
//遍历这个“桶”,放入到原数组之中
for (int k = 0; k < bucketElementCount[j]; k++) {
array[index++] = bucket[j][k];
}
}
//第i+1轮之后,需要将每一个bucketElementCount[j] = 0 !!!!!!
bucketElementCount[j] = 0;
}
System.out.println("第" + (i + 1) + "轮之后对array的排序:\n" + Arrays.toString(array));
}
return array;
}
}
7.2、小结:
1、是通过键值的各个位的值,将要排序的元素分配至一些“桶”里面,以此达到排序的作用
2、基数排序是高效率的稳定性排序,是桶排序的一种扩展,速度很快
3、基数排序是一种经典的用空间换取时间的算法,当所处理的数据非常大的时候,很容易出现OutOfMemoryError错误
3、基数排序是稳定的
何为稳定:
假设在一个数组之中有多个相同值的元素,经过排序之后,排序前在前面的元素,排序后依然是在前面,则说明该排序算法是稳定的
8、堆排序
8.1、实现的思路:
实现的思路:
1、将待排序的数组构造成一个大顶堆或者小顶堆
2、这个时候的最大值(最小值)就是堆顶的根节点
3、将这个最大的元素与最后一个元素交换,这样最后一个元素就是最大的值了
4、将剩下的 n-1个元素重新构造成一个大顶堆或小顶堆,重复上述的步骤,最后就形成了一个有序的序列
public class HeapSorting {
public static void main(String[] args) {
int[] array = {9, 78, 0, 23, 567, 70};
heapSorting(array);
}
//实现堆排序
public static void heapSorting(int[] array) {
System.out.println("堆排序!");
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap(array, i, array.length);
}
System.out.println("最终得到的数组:\n" + Arrays.toString(array));
}
/**
* 实现将数组转换为大顶堆
*
* @param array 需要转换的数组
* @param i 当前这个非叶子节点在数组之中的索引
* @param length 还要对多少个元素进行调整
*/
public static void adjustHeap(int[] array, int i, int length) {
//先将要调整的元素保存
int temp = array[i];
//j是指向当前节点的左节点
for (int j = i * 2 + 1; j < array.length; j += i * 2 + 1) {
//当前节点的左节点小于当前节点的右节点
if (j + 1 < length && array[j] < array[j + 1]) {
//将j指向当前节点的右节点
j++;
}
//当前节点的子节点大于当前节点
if (array[j] > temp) {
//把子节点的值赋给父节点
array[i] = array[j];
//将i指向j,继续循环比较
i = j;
} else {
break;
}
}
//这个时候array[i]已经是指向了该树的最大值且放在了树的根部,将temp放到这个地方
array[i] = temp;
}
}
8.2、小结:
1、
每个节点的值都大于它的子节点的值称为大顶堆(array[i] >= array[2*i + 1] && array[i] >= array[2*i + 2])
每个节点的值都小于它的子节点的值称为小顶堆(array[i] <= array[2*i + 1] && array[i] <= array[2*i + 2])
2、堆排序是一种选择排序、不稳定的排序