排序算法总结(冒泡、选择、插入、希尔、快速、归并、基数、堆排序)
1.冒泡排序
* 基本思想:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值
* 若发现逆序则交换,使值较大的元素逐渐从前向后移,就像水底的气泡一样逐渐向上冒
* 因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,那说明序列有序,
* 因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(冒泡【排序优化)
public static void BubbleSort(int[] n){
for (int i = 0; i < n.length-1; i++) {
for (int j = 0; j <(n.length-1)-i ; j++) {
if(n[j]>n[j+1]){
int temp =n[j];
n[j] = n[j+1];
n[j+1] = temp;
}
}
}
System.out.println("n:"+ Arrays.toString(n));
}
public static void BubbleSortOptimize(int[] n){
//,如果一趟比较下来没有进行过交换,那说明序列有序,
boolean flag = false; //记录一趟是否发生改变
for (int i = 0; i < n.length-1; i++) {
for (int j = 0; j <(n.length-1)-i ; j++) {
if(n[j]>n[j+1]){
flag = true;
int temp =n[j];
n[j] = n[j+1];
n[j+1] = temp;
}
}
if(!flag){
break;
}else
{
flag = false;
}
}
}
2.选择排序
*选择排序思想: 属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,(最小或者最大)再依规定交换位置后达到排序的目的。
* 说明:
* 1.选择排序一共有数组大小-1 轮排序
* 2.每一轮排序,又是一个循环,循环的规则是
* 2.1先假定当前这个数是最小数
* 2.2然后和后面的每一个数进行比较,如果发现有比当前数更小的数,就重新却时那个最小数,并得到下表
* 2.3当遍历到数组的最后是,就得到本轮最小数和下标
* 2.4交换
* */
public static void selectSort(int[] arr){
for (int j = 0; j <arr.length-1 ; j++) {
int minIndex = j;
int minValue = arr[j];
for (int i = j+1; i <arr.length ; i++) {
if(minValue>arr[i]){
minValue = arr[i];
minIndex = i;
}
}
if(minIndex != j){
arr[minIndex] = arr[j];
arr[j] = minValue;
}
}
System.out.println(Arrays.toString(arr));
}
3.插入排序
* 插入排序(InsertSorting):是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的
*
*思想:把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素,
* 无序表中包含有n-1个元素,排序过程中每个从无序表中取出第一个元素,
* 把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置, 使之成为新的有序表。
*
public static void InsertSort(int[] arr){
for (int i = 1; i <arr.length ; i++) {
int insertVal = arr[i]; //定义待插入的数
int insertIndex = i-1; //arr[i] 前面这个数的下标
while(insertIndex>=0&& insertVal <arr[insertIndex]){
//1.保证insertIndex >=0保证在给insertVal找插入位置,不越界
//2.insertVal <arr[insertIndex] 待插入的数,还没有找到插入位置, 将arr[insertIndex]后移
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
//退出while循环时,说明插入的位置找到,insertIndex+1
if(insertIndex+1 ==i){
arr[insertIndex+1] = insertVal;
}
arr[insertIndex+1] = insertVal;
}
}
4. 希尔排序
*希尔排序时候为了解决 插入排序的缺点
* 插入排序的缺点: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。
*
希尔排序(Donald Shell):也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也叫缩小增量排序
* 基本思想:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词
*
public 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++) {
// 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap
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;
}
}
}
//System.out.println("希尔排序第" + (++count) + "轮 =" + Arrays.toString(arr));
}
}
//对交换式的希尔排序进行优化->移位法
public static void shellSort2(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];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
//移动
arr[j] = arr[j-gap];
j -= gap;
}
//当退出while后,就给temp找到插入的位置
arr[j] = temp;
}
}
}
}
5.快速排序
快速排序: 是对冒泡排序的一种改进。
思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按照此方法对两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
public static void quickSort(int[] arr,int left, int right) {
int l = left; //左下标
int r = right; //右下标
//pivot 中轴值
int pivot = arr[(left + right) / 2];
int temp = 0; //临时变量,作为交换时使用
//while循环的目的是让比pivot 值小放到左边
//比pivot 值大放到右边
while( l < r) {
//在pivot的左边一直找,找到大于等于pivot值,才退出
while( arr[l] < pivot) {
l += 1;
}
//在pivot的右边一直找,找到小于等于pivot值,才退出
while(arr[r] > pivot) {
r -= 1;
}
//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
//小于等于pivot值,右边全部是大于等于pivot值
if( l >= r) {
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
if(arr[l] == pivot) {
r -= 1;
}
//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
if(arr[r] == pivot) {
l += 1;
}
}
// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if(left < r) {
quickSort(arr, left, r);
}
//向右递归
if(right > l) {
quickSort(arr, l, right);
}
}
6.归并排序
归并排序(Merge-sort):是利用归并的思想实现的排序方法,该方法采用经典的分治(divide-and-conquer)策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案“修补”在一起,即分而治之)
//分+合方法
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, mid, right, temp);
}
}
//合并的方法
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public 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数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到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
temp[t] = arr[i];
t += 1;
i += 1;
}
while( j <= right) { //右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
7.基数排序
1.基数排序:属于“分配式排序(distribution sort)”又称“桶子法(bucket sort)”顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。
2.基数排序(Radix sort)是桶排序的扩展
3.基数排序是属于稳定性的排序,基数排序法是效率高的稳定排序法
4.基数排序是1887年赫尔曼发明的
1.基数排序:属于“分配式排序(distribution sort)”又称“桶子法(bucket sort)”顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。
2.基数排序(Radix sort)是桶排序的扩展
3.基数排序是属于稳定性的排序,基数排序法是效率高的稳定排序法
4.基数排序是1887年赫尔曼发明的
基本思想:1.将所有带比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
//基数排序方法
public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
//1. 得到数组中最大的数的位数
int max = arr[0]; //假设第一数就是最大数
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
//说明
//1. 二维数组包含10个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
//3. 名明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
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) {
//循环该桶即第k个桶(即第k个一维数组), 放入
for(int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
//System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));
}
}
总结:
推荐阅读
-
用Python代码实现插入排序、希尔排序、冒泡排序、快速排序、直接选择排序、堆排序、归并排序、基数排序
-
Java数组排序总结(冒泡,选择,插入,希尔) JavaJ#算法Blog工作
-
Java数组排序总结(冒泡,选择,插入,希尔) JavaJ#算法Blog工作
-
第3章——排序【冒泡、选择、插入、希尔、归并、快速排序、排序的稳定性等】
-
稳定性分析与Java实现:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序
-
排序(插入、希尔、冒泡、快速、选择、堆排、归并、计数及排序性能比较和稳定性)
-
排序算法——选择,插入,冒泡,希尔,快排,归并
-
Java——(排序算法)冒泡、选择、插入、快排、归并、希尔、基数
-
排序算法总结(冒泡、选择、插入、希尔、快速、归并、基数、堆排序)
-
七大排序算法比较【冒泡、选择、插入、希尔、快排、归并,堆】