排序算法:
排序也称排序算法,排序是将一组数据,依指定的顺序进行排列的过程
排序的分类
1,内部排序
指将需要处理的所有数据都加载到内部存储器(内存)中进行排序
2,外部排序
数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序
常见的排序算法:
一.插入排序:
- 1.直接插入排序
/**
* 直接插入排序
* <p>
* 算法描述
* 从第一个元素开始,该元素可以认为已经被排序
* 取出下一个元素,在已经排序的元素序列中从后向前扫描
* 如果该元素小于扫描的元素,那么就把该元素插入到扫描的元素之前
* 重复步骤
* <p>
* <p>
* 比较与总结
* 插入排序所需的时间取决于输入元素的初始顺序。
* 例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比随机顺序的数组或是逆序数组进行排序要快得多。
*/
public class InsertSort {
/**
* 优化后
*
* @param array
*/
public void sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j > 0; j--) {
//当前元素
int cur = array[j];
//上一个元素
int pre = array[j - 1];
//当前元素小于上一个,那么就换位置
if (cur < pre) {
int temp = cur;
array[j] = pre;
array[j - 1] = temp;
}
//前面的位置已经拍好了
else {
break;
}
}
}
}
}
- 2.希尔排序
/**
* 希尔排序
* <p>
* 将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;
* 每次再将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
* <p>
* 希尔排序其实就是简单插入排序的进阶版本,简单插入排序在数据量大且数据数据无序的情况下表现不好
* <p>
* 那么就先将数组分组排序之后,在用插入排序的方法进行排序
* <p>
* 总结与思考
* 希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,
* 排序之后子数组都是部分有序的,这两种情况都很适合插入排序。
*/
public class ShellSort {
/**
* 希尔排序
*
* @param array
*/
public void sort(int[] array) {
//分成多组
//定义分组系数
int offset = 2;
int gap = array.length / offset;
while (gap > 0) {
//对每个分组进行插入排序
insertSort(array, gap);
System.out.println("gap=" + gap + "array=" + Arrays.toString(array));
gap = gap / offset;
}
}
/**
* 插入排序
* 当grap==1的时候,就是插入
*
* @param array
* @param grap
*/
private void insertSort(int[] array, int grap) {
for (int i = 0; i < array.length - grap; i++) {
for (int j = i + grap; j >= grap; j = j - grap) {
int cur = array[j];
int pre = array[j - grap];
if (cur < pre) {
int temp = cur;
array[j] = pre;
array[j - grap] = temp;
} else {
break;
}
}
}
}
}
二.选择排序:
- 1.简单选择排序
/**
* 简单选择排序
* 基本思想
* 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,
* 存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,
* 然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
* <p>
* 选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。
* 选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,
* 因此对 n个元素的表进行排序总共进行至多 n-1 次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
* <p>
* 算法描述
* 从未排序序列中,找到关键字最小的元素
* 如果最小元素不是未排序序列的第一个元素,将其和未排序序列第一个元素互换
* 重复1、2步,直到排序结束。
* <p>
* <p>
* 总结与思考
* 选择排序的简单和直观名副其实,这也造就了它”出了名的慢性子”,
* 无论是哪种情况,哪怕原数组已排序完成,它也将花费将近n²/2次遍历来确认一遍。
* 即便是这样,它的排序结果也还是不稳定的。 唯一值得高兴的是,它并不耗费额外的内存空间。
*/
public class SelectionSort {
/**
* 简单选择排序
*
* @param array
*/
public void simpleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
//默认i索引位置的数最小,然后找更小的数
int min = i;
//找出最小的数,放在第一位,每轮确定一个数
for (int j = i + 1; j < array.length; j++) {
//拿到这轮循环最小的值的索引min
if (array[j] < array[min]) {
min = j;
}
}
//交换
if (min != i) {
int cur = array[i];
array[i] = array[min];
array[min] = cur;
}
}
}
}
- 2.堆排序
三.交换排序
- 1.冒泡排序(easy)
/**
* 冒泡排序
* 基本思想
* 冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,
* 如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
* 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
*
*
* 冒泡排序算法的运作如下:
*
* 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
* 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
* 针对所有的元素重复以上的步骤,除了最后一个。
* 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
*
* 第一次排序后最后的元素会是最大的数,第二次排序后,确定最后两位数.
* 最后位置已经确定,不需要再次排序
* 使用标识对算法进行优化,如果循环一遍,没有需要交互的数,那么整个顺序就不需要继续排
*
*
* 总结与思考
* 由于冒泡排序只在相邻元素大小不符合要求时才调换他们的位置,
* 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序算法。
*/
public class BubbleSorting {
/**
* 简单的冒泡排序
*
* @param array
*/
public void simpleSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
//每次排序完一次,那么最后一个值固定了,就可以少排一次
for (int j = 0; j < array.length - 1 - i; j++) {
//交互位置
int cur = array[j];
int next = array[j + 1];
//交互位置
if (cur > next) {
int temp = cur;
array[j] = next;
array[j + 1] = temp;
}
}
}
}
/**
* 优化后
*
* @param array
*/
public void sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
//每次排序完一次,那么最后一个值固定了,就可以少排一次
for (int j = 0; j < array.length - 1 - i; j++) {
//交互位置
int cur = array[j];
int next = array[j + 1];
//是否发生过交换,如果一次循环一次交换都没有,那么证明排序完毕
boolean flags = false;
//交互位置
if (cur > next) {
int temp = cur;
array[j] = next;
array[j + 1] = temp;
flags = true;
}
//没有发生交换
if (!flags) {
break;
}
}
}
}
}
- 2,快速排序
/**
* 快速排序
* 快速排序,见名思意就是排序速度快
* <p>
* 其实也可以理解为挖坑,填坑
* 首先找到一个基准元素,将比元素大的值,全部拍到元素左边,比元素小的数,全部拍到元素右边
* 这样该元素就拍到了中间,左边的都比这个元素小,右边的都比这个元素大
* 然后元素左边的,和元素右边的,都重复这个过程
* 最后每段只剩下一个元素了,那么排序完毕
*/
public class QuickSort {
/**
* 快速排序
*
* @param arr
* @param start 开始索引
* @param end 结束索引
*/
public void quickSort(int[] arr, int start, int end) {
//一个元素的时候,不需要排了
if (start >= end) {
return;
}
int low = start;
int high = end;
//左边的第一个元素定义为基准
int temp = arr[low];
//那么开始查找,一定得从右向左查找
while (low < high) {
//找到小于temp 位置的索引
while (high > low && arr[high] >= temp) {
high--;
}
//找到一个,那么就把这个值挪动到左边
if (low < high) {
arr[low] = arr[high];
//然后左边的位置挪动一位
low++;
}
//从左向右边找
while (high > low && arr[low] < temp) {
low++;
}
if (low < high) {
arr[high] = arr[low];
//右边的位置左移一位
high--;
}
}
//退出循环,肯定是相遇了,把基准数移动到这个位置
arr[low] = temp;
//然后递归调用
//找数组左边的
//左边的其实就是基准位置索引左边的
quickSort(arr, start, low - 1);
//找右边的,基准数右边的,第一次是数组长,后面就是传入的最右边的索引位置了,这个值不断变化的
quickSort(arr, low + 1, end);
}
}
四.归并排序
/**
* 归并排序
* 跟快速排序有点类似
* 快速排序是找一个基准,基准左边是小于的数,右边都是大于基准的数,通过不断的分组
* 把每一组的顺序排好
* 最后分成每个分组只有一个元素了,那么排序完了
* <p>
* 规并排序是利用了额外的空间
* 直接将数组不断的细分,最后分到只有一个元素为止
* 然后不断通过合并这些数组,就得到了一个有序的数组
* 把有序的数组拷贝到之前的数组中去,拷贝之前,之前的数组序列不变
*/
public class MergeSort {
/**
* 对数组进行分组,然后排序
*
* @param arr
* @param start
* @param end
*/
public void sort(int[] arr, int start, int end) {
if (start >= end) {
return;
}
int low = start;
int high = end;
//数组拆分位置的索引
int mid = (low + high) / 2;
//继续分组
//向左递归进行分解
sort(arr, low, mid); //向右递归进行分解
sort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
/**
* 进行合并
*
* @param arr
* @param start
* @param mid
* @param end
*/
public void merge(int[] arr, int start, int mid, int end) {
int[] temp = new int[end - start + 1];
//定义两个指针,p1,p2 ,p1指向左边数组的开头, p2 指向右边数组的开头
int p1 = start;
int p2 = mid + 1;
//指向缓存数组的索引
int k = 0;
//合并两组为一组
while (p1 <= mid && p2 <= end) {
if (arr[p1] <= arr[p2]) {
temp[k] = arr[p1];
p1++;
} else {
temp[k] = arr[p2];
p2++;
}
k++;
}
//两个数组中没有复制完的部分
while (p1 <= mid) {
temp[k] = arr[p1];
k++;
p1++;
}
while (p2 <= end) {
temp[k] = arr[p2];
k++;
p2++;
}
int index = start;
System.out.println("temp=" + Arrays.toString(temp));
//因为start--end直接已经拍好了,所以先把这部分复制回原来的数组
//复制到指定的位置
for (int i = 0; i < temp.length; i++) {
arr[index] = temp[i];
index++;
}
System.out.println("arr=" + Arrays.toString(arr));
}
}
五.基数排序
/**
* 堆排序
* 堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。
* 一是建堆函数,二是反复调用建堆函数以选择出剩余未排元素中最大的数来实现排序的函数。
* 操作如下:
* a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
* b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
* c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
* <p>
* 跟选择排序有点类似,堆排序是每次选出最大的放在数组最后边
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr = new int[]{20, 17, 14, 1, 45, 4, 25, 2, 0, 60};
sort(arr);
System.out.println("排序后=" + Arrays.toString(arr));
}
/**
* 进行排序
*
* @param arr
*/
private static void sort(int[] arr) {
//从尾向头构建大顶堆
//特别注意,i必须大于0,当i==0的时候,所有的元素都排好位置了
//0为根节点,0 后的子节点的值必然比0的值大,双方位置替换后,swap(arr,0,0)也换不回去,必然出错
for (int i = arr.length - 1; i > 0; i--) {
heapify(arr, i);
//构建大顶堆后,arr[0] 为树中的最大值
//把该值换到数组的最后
swap(arr, 0, i);
}
}
/**
* 数组中的位置互换
*
* @param arr
* @param i
* @param j
*/
private static void swap(int[] arr, int i, int j) {
if (i == j) {
return;
}
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 构建大顶堆
* 构建完毕后,对顶的数据为数组最大值
*
* @param arr
* @param lastIndex
*/
private static void heapify(int[] arr, int lastIndex) {
//通过最后一个索引获取大顶堆最后一个非叶子节点的索引
int nodeIndex = (lastIndex - 1) / 2;
//从下向上构建大顶堆
for (int i = nodeIndex; i >= 0; i--) {
//获取该非叶子节点,获取左子节点的位置
//如果存在右子节点的话,比较两个子节点,取得数值大的位置的索引位置
int left = 2 * i + 1;
//左节点必然存在,右节点不一定存在
//当left==lastIndex 时,右子节点就不存在
if (left != lastIndex) {
//当右节点存在的时候
int right = 2 * i + 2;
//比较左右子节点的数据大小,进行索引替换
if (arr[left] < arr[right]) {
left = right;
}
}
//比较子节点与父节点的大小,大的数据上移
if (arr[i] < arr[left]) {
int temp = arr[i];
arr[i] = arr[left];
arr[left] = temp;
}
}
}
}