排序算法
一、分类
排序算法总结:
►插入排序:直接插入排序、折半插入排序、希尔排序
►交换排序:冒泡排序、快速排序
►选择排序:简单选择排序、堆排序
►归并排序
非比较类排序:
►基数排序
►计数排序、桶排序(要求排序的数据是有确定范围的整数)
二、比较
三、算法代码
//直接插入排序:第一个元素认为是有序的,然后向后一个个一个扩展将有序部分扩大
public void insertSort(int[] nums){
int temp;
//一个数默认有序
for (int i=1;i<nums.length;i++){
//记录当前需要插入的数据
temp=nums[i];
int j=i;
//当前数据与前面的所有数据比较,前面数据比当前数据大的就往后移位
while (j>0 && temp<nums[j-1]){
nums[j]=nums[j-1];
j--;
}
//比较结束,j的位置前面比当前数小,后面比当前数据大
nums[j]=temp;
}
}
// 折半插入排序:直接插入排序的改进,需要插入的数据与已排好序的数据的中间开始比较
public void halfInsertSort(int[] nums){
int temp;
//一个数默认有序
for (int i=1;i<nums.length;i++){
//记录当前需要插入的数据
temp=nums[i];
int j=i;
int left=0,right=i-1;
//取到等号:如果不取等号,left=right时的值未与temp进行比较,比较不完整
while (left<=right){
//不断折断,寻找中间位置
int mid = (right+left)/2;
if (temp<=nums[mid])
//此时,nums[mid]的值比temp大,因此right从mid-1处重新开始计算
right = mid-1;
else
//此时,nums[mid]的值比temp小,因此left从mid+1处重新开始计算
left = mid+1;
}
for (;j>left;j--){
nums[j]=nums[j-1];
}
//比较结束,j的位置前面比当前数小,后面比当前数据大
nums[left]=temp;
}
}
// 希尔排序:插入排序的再次改进
// 通过比较相距一定间隔的元素进行比较排序,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止
public void shellKnuthSort(int[] nums){
int temp,gap=nums.length/2;
while (gap>0){
for (int i=gap;i<nums.length;i++){
temp = nums[i];
//取前一个数
int pre = i-gap;
while (pre >= 0 && temp < nums[pre]){
// 为什么取到[pre+gap]:进入循环表明temp比前一个间隔的数据小
// 与插入排序相同的就是,希尔排序每个间隔、分组,从头开始排序,在这个分组中,前面部分已轮询的是已经排好序的
nums[pre+gap]=nums[pre];
pre = pre-gap;
}
// 此时的 pre+gap 处以及往后的数据是比temp大的,需要向后移
nums[pre+gap] = temp;
}
gap/=2;
}
}
// 冒泡排序:按顺序前后两个对比,大的在前就调换顺序
public void BubbleSort(int[] nums){
int temp;
for (int i = 0; i < nums.length; i++){
//去除后面i个已经排好的元素,要 -1 不然会越界
for (int j = 0;j<nums.length-i-1;j++){
if (nums[j]>nums[j+1]){
temp = nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
}
}
}
/* 一次快排的举例:
23 46 0 8 11 18
temp=23
18 46 0 8 11 18
18 46 0 8 11 46
18 11 0 8 11 46
18 11 0 8 23 46
10 46 0 8 11 18
temp=10
8 46 0 8 11 18
8 46 0 46 11 18
8 0 0 46 11 18
8 0 10 46 11 18*/
//快速排序:是对冒泡排序的一种改进
// 通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小
public void quickSort(int[] nums,int left,int right){
if (left<right){
// 经历一次快排,并得到基准数的位置索引
int index = getIndex(nums,left,right);
// 分块迭代,两边分别进行快排迭代
quickSort(nums,left,index-1);
quickSort(nums,index+1,right);
}
}
public int getIndex(int[] nums,int left,int right) {
//取基准数 1,2,2,3,0,3 temp=3
int temp = nums[left];
while (left<right){
//从右边开始比,因为左边的数作为基准数了
//取等号!!!
// 为什么:如果数组中有相同的两个数,会陷入死循环,left 和 right 都无法再增减,而 left<right 一直为 true
while (left<right && temp <= nums[right]){
right--;
}
nums[left]=nums[right];
//取等号!!!
while (left<right && temp >= nums[left]){
left++;
}
nums[right]=nums[left];
}
nums[left]=temp;
return left;
}
//简单选择排序:不停的去寻找最大或最小的元素
public void selectionSort(int[] nums){
for (int i=0;i<nums.length;i++){
// 标记最小元素的位置索引
int minIn = i;
for (int j=i;j<nums.length;j++){
if(nums[minIn]>nums[j]){
minIn = j;
}
}
int temp=nums[i];
nums[i]=nums[minIn];
nums[minIn]=temp;
}
}
/*
* 堆排序:
* 什么是堆:
* · 堆是一个完全二叉树
* · 堆中每一个节点的值必须大于或者等于(小于或者等于)其左右子树的值。【大顶堆、小顶堆】
*
* 构建大顶堆或小顶堆,将最大数或者最小值与末尾的值互换后取出,重新构建大顶堆或小顶堆,重复。
* */
public void heapSort(int[] nums){
/*数组构建堆:{2,6,3,0,9,7,1,2} → {9, 6, 7, 2, 2, 3, 1, 0}
* 第一个数据为堆顶,因为是大顶堆,所以为本数组中的最大数
* 第二三个数据分别是第一个数据的左右子树,6,7
* 2,2 两个数是第二个数即6的左右子树;3,1 两个数是第三个数即7的左右子树
* 以此类推
* */
maxheapify(nums,nums.length);
for(int i=1;i<nums.length;i++){
int temp=nums[0];
nums[0]=nums[nums.length-i];
nums[nums.length-i]=temp;
maxheapify(nums,nums.length-i);
}
}
private void maxheapify(int[] arr, int len) {
int i;
if(len%2==0){
i=len/2;
}else {
i=(len-1)/2;
}
//要从尾部开始比较,不能从头部开始比较
for(;i>0;i--){
heapify(arr,i,len);
}
}
private void heapify(int[] arr,int i, int len) {
int left,right,lagest;
left=2*i-1;
right=2*i;
lagest=i-1;
if (left<len&&arr[lagest]<arr[left]){
lagest=left;
}
if (right<len && arr[lagest]<arr[right]){
lagest=right;
}
if (lagest!=i-1){
int temp = arr[lagest];
arr[lagest] = arr[i-1];
arr[i-1] = temp;
//因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
heapify(arr,i, len);
}
}
// 归并排序:归并法,先使子序列有序,再将两个子序列合并使其有序,直至整个序列有序
public int[] mergeSort(int[] nums){
if (nums.length <= 1) return nums;
int mid = nums.length / 2;
//分为左右两部分,左闭右开
int[] left = Arrays.copyOfRange(nums, 0, mid);
int[] right = Arrays.copyOfRange(nums, mid, nums.length);
//递归
return merge(mergeSort(left), mergeSort(right));
}
//合并
public int[] merge(int[] left, int[] right) {
//创建新数组保存合并数据
int[] result = new int[left.length + right.length];
//用三个指针来合并数组,分别指向合并后的数组、左数组和右数组
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
//左数组为空或者全部添加完毕,直接复制右数组
if (i >= left.length)
result[index] = right[j++];
//右数组为空或者全部添加完毕,直接复制左数组
else if (j >= right.length)
result[index] = left[i++];
//右数组当前元素小,添加右数组元素
else if (right[j] < left[i])
result[index] = right[j++];
//左数组当前元素小,添加左数组元素
else
result[index] = left[i++];
}
return result;
}
/*
* 基数排序 vs 计数排序 vs 桶排序
* 这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
* 基数排序:根据键值的每位数字来分配桶;
* 先以个位数的大小来对数据进行排序,接着以十位数的大小来多数进行排序,接着以百位数的大小……
* 计数排序:每个桶只存储单一键值;(适合于最大值和最小值的差值不是不是很大的排序)
* 把数组元素作为数组的下标,然后用一个临时数组统计该元素出现的次数,例如 temp[i] = m, 表示
* 元素 i 一共出现了 m 次。最后再把临时数组统计的数据从小到大汇总起来,此时汇总起来是数据是有序的。
* 桶排序:每个桶存储一定范围的数值;
* 桶排序就是把最大值和最小值之间的数进行瓜分,例如分成 10 个区间,10个区间对应10个桶,
* 我们把各元素放到对应区间的桶中去,再对每个桶中的数进行排序,可以采用归并排序,也可以采
* 用快速排序之类的。
* */
// 基数排序
public void countSort(int[] nums){
int max=nums[0],n=nums.length;
// 找出最大值
for (int i =1;i<n;i++){
if (max<nums[i])
max=nums[i];
}
//计算最大值为几位数
int num = 1;
while (max / 10 > 0) {
num++;
max = max / 10;
}
// 创建10个桶
ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(10);
//初始化桶
for (int i = 0; i < 10; i++) {
bucketList.add(new LinkedList<Integer>());
}
// 进行每一趟的排序,从个位数开始排
for (int i = 1; i <= num; i++) {
for (int j = 0; j < n; j++) {
// 获取每个数最后第 i 位是数组
int radio = (nums[j] / (int)Math.pow(10,i-1)) % 10;
//放进对应的桶里
bucketList.get(radio).add(nums[j]);
}
//合并放回原数组
int k = 0;
for (int j = 0; j < 10; j++) {
for (Integer t : bucketList.get(j)) {
nums[k++] = t;
}
//取出来合并了之后把桶清光数据
bucketList.get(j).clear();
}
}
}
// 我的下意识想法
public void mysort(int[] nums){
int temp;
for (int i=0;i<nums.length;i++){
for (int j=i+1;j<nums.length;j++){
if (nums[i]>nums[j]){
temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
}
}