推荐学习链接:https://www.bilibili.com/video/BV1Wq4y1Z7yi?p=1
选择排序
/**
* 选择排序 - (时间复杂度 n2) - (空间复杂度 1) - 不稳定 :
* 记录当前 未排序部分 最小值的 下标 ,
* 然后,与正确位置处的值进行交换。
*/
public class 选择排序 {
private static void sort(int[] nums){
int minIndex = 0; //记录当前 未排序部分 最小值的 下标
for (int i = 0; i < nums.length-1 ; i++) {
minIndex = i;
//更新 未排序部分 最小值的下标
for (int j = i+1; j < nums.length; j++) {
if (nums[minIndex] > nums[j]) minIndex = j;
}
int t = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = t;
}
}
}
插入排序
/**
* 插入排序 - (时间复杂度 n2) - (空间复杂度 1) - 稳定 :
* 保证前面 部分数据有序,将后面数据依次与前面有序部分的数据进行比较,移动,
* 然后,将此待排数据插入到前面合适的位置中。
*/
public class 插入排序 {
private static void sort(int[] nums){
int temp;
for (int i = 1; i < nums.length; i++) {
temp = nums[i]; //现在要插入i位置上的牌
int j ; //i前面的部分是有序的
for (j = i-1; j >=0 && temp < nums[j]; j--) {
nums[j+1] =nums[j];
}
nums[j+1] = temp;
}
}
}
冒泡排序
/**
* 冒泡排序 - (时间复杂度 n2) - (空间复杂度 1) - 稳定 :
* 每次循环 ,在前面未排序部分,比较相邻数据的大小,
* 通过相邻数据之间的比较以及交换,每次循环结束都可以把当前未排序部分的最大值移动到后面。
* 后面是有序部分,而且有序部分会越来越多,最后整个数组就是有序数组了。
*/
public class 冒泡排序 {
private static void sort(int[] nums){
for(int i = nums.length;i > 0;i--){
for(int j = 1;j < i;j++){
if(nums[j-1] > nums[j]){
int t = nums[j];
nums[j] = nums[j-1];
nums[j-1] = t;
}
}
}
}
}
希尔排序
/**
* 希尔排序 - (时间复杂度 n(1.3) ) - (空间复杂度 1) - 不稳定 :
* 希尔排序 是对 插入排序 的一种改进和升级。
* 希尔排序利用插入排序的思想,在插入排序的基础上:
* 将整个序列以固定间隔,分成若干个子序列,首先在子序列内部分别进行插入排序,
* 然后,再不断的缩小间隔,直到最后间隔为1时,对整个序列进行一次插入排序。
*/
public class 希尔排序 {
private static void sort(int[] nums) {
for (int d = nums.length/3; d >0 ; d = d/3) { //d表示间隔,最后d=1时,表示对整个序列进行最终排列
for (int i = d; i < nums.length; i++) { //处理 下标为i处的数据,
for (int j = i-d; j >=0 ; j=j-d) { //进行插入排序:将下标为i的数据,插入到前面以d为间隔的有序子序列当中
if (nums[j+d] < nums[j]){
int t = nums[j+d];
nums[j+d] = nums[j];
nums[j] = t;
}
}
}
}
}
}
归并排序
/**
* 归并排序 - (时间复杂度 n *(log n) ) - (空间复杂度 n) - 稳定 :
* 归并排序的思想是:
* 首先,把整个数组一分为二,分成两个子序列,
* 然后,再对每个子序列进行分割,
* 直到每个子序列中都只有一个元素时(单个元素的序列可以认为是有序的子序列),
* 然后再依次合并两个有序子序列 ,成为一个更大的有序子序列,
* 直到最终合并成一整个有序序列。
*
* Java里,进行 对象排序的 Arrays.sort (T[] a , Comparator<? super T> c) 方法,
* JDK 1.8 以前,用的是上面说的,普通的归并排序,
* JDK 1.8 开始,使用 TimSort.sort()方法,此方法先在局部采用二分插入排序,然后再使用多路归并排序。
* TimSort.sort()方法中,规定 子数组的最小长度为 MIN_MERGE。
* 多路归并排序的算法思想是,直接将原数组按照 MIN_MERGE长度,分割成多个子数组(不是用递归一点一点的分割),
* 长度小于 MIN_MERGE的子数组,调用binarySort方法进行二分插入排序 (向有序部分插入待排数据时,使用二分法寻找插入位置)。
* 所有 MIN_MERGE长度的子数组内部有序了之后,再两两合并、排序,最终实现整个数组有序。
*/
public class 归并排序 {
private static void sort(int[] nums){
div(nums,0,nums.length-1);
}
/**
* div() 是个 递归函数:
* 作用是,将当前数组一分为二,形成两个子数组,
* 然后再对子数组进行分割,直到每个子数组都只有一个元素为止。
* start 是开始下标 (包含),
* end 是结束下标 (包含)。
*/
private static void div(int[] nums, int start, int end){
if (end == start) return; //表示当前序列只有一个元素,递归结束
int mid = (end - start)/2 + start;
div(nums,start,mid); //继续分割前半部分
div(nums,mid+1,end); //继续分割后半部分
merge(nums,start,mid,end); //合并两个有序子序列
}
/**
* merge方法的作用是,合并两个有序的子数组:
* 第一个子数组:以 start 开始(包含),以 mid结束(包含)。
* 第二个子数组:以 mid+1 开始(包含),以 end结束(包含)。
*/
private static void merge(int[] nums, int start, int mid, int end) {
int i = start,j = mid+1; //分别用 i和j, 记录两个有序数组的开始下标
int[] temp = new int[end-start+1]; //临时保存合并之后的数据
int p = 0; //记录临时数组temp的下标位置
while (i <= mid && j <= end){
temp[p++] = (nums[i] <= nums[j]) ? nums[i++] : nums[j++];
}
//处理 某个子数组的剩余部分
while (i <= mid) temp[p++] = nums[i++];
while (j <= end) temp[p++] = nums[j++];
//将合并结果传给原数组
for (int k = 0; k < temp.length; k++) {
nums[start+k] = temp[k];
}
}
}
快速排序
/**
* 快速排序 - (时间复杂度 n *(log n)) - (空间复杂度 log n) - 不稳定 :
* 算法思想:
* 每次选取一个轴线值,然后分别从前和后两个方向,分别向中间移动前后指针,
* 如果前面的指针在移动的过程中,遇到了比轴线值大的数,就立即停止,
* 如果后面的指针在移动的过程中,遇到了比轴线值小的数,也立刻停止,
* 此时,两个指针所在下标处的值进行交换,交换完之后,前后指针继续向中间移动,
* 一直持续上述过程,直到两个指针相遇为止,表示一次快速排序完成。
*
* 注意:每完成一次快速排序后的结果:
* 1.轴线值一定会在正确的位置上,
* 2.此外,轴线前面的数一定小于等于轴线值,轴线后面的数一定大于等于轴线值。
*
* 然后,根据上一轮排序完成后的轴线下标,
* 将数组分割成前后两个子数组,再分别对子数组进行快速排序。
* 直到每个子数组都只有一个元素为止,表示整个数组已经完全有序。
*/
public class 快速排序 {
private static void sort(int[] nums){
div(nums,0,nums.length-1);
}
/**
* div() 是个 递归函数,其作用是:
* 在以 start 开始(包含),以 end 结尾(包含) 的数组中,
* 先调用 partition()函数, 对当前数组进行一次快速排序,并获得排序完成后,轴线数据所在的正确下标。
* 然后,再将数组按照轴线所在下标,分割成前后两个子数组,再调用partition()函数进行快速排序。
* 直到最后每个子数组都只有一个元素为止。
*/
private static void div(int[] nums, int start, int end) {
if (start >= end) return; //表示前后指针已经相遇,递归结束
int mid = partition(nums,start,end);
div(nums,start,mid-1); //继续处理前半部分
div(nums,mid+1,end); //继续处理后半部分
}
/**
* partition()方法 的作用是:
* 在以 start 开始(包含),以 end 结尾(包含) 的数组中,
* 始终以 下标为start的数据(第一个数据) 作为轴线,进行一次快速排序,
* 并返回排序完成后,轴线数据所在的正确位置下标。
* 快速排序的思想是:
* 首先有一个指针left,left从第二个元素开始向后搜索,当找到比轴线大的数据时就停下来,
* 然后,让从后往前找的指针right向前移动,当找到比轴线小的数据时,也停下来,
* 此时,交换left和right所在下标中的值。
* left指针始终向后移动,right指针始终向前移动,直到left=right为止。
* 最后,再将 轴线值 与 两指针交界处的值 进行判断,
* 把 轴线值换到 正确的位置上去,
* 并将轴线值所在的正确下标位置,作为返回值返回。
*/
private static int partition(int[] nums, int start, int end) {
int pivot = nums[start]; //选取子数组的第一个数作为轴值
int left = start+1 , right = end;
while (left < right){
while (left < right && nums[left] <= pivot) left++;
while (left < right && nums[right] >= pivot) right--;
//此时,下标为left的数 大于轴值pivot,下标为right的数 小于轴值pivot。二者进行值交换
if (left < right){
int t = nums[left];
nums[left] = nums[right];
nums[right] = t;
}
}
//此时 left = right,最后再把 轴值pivot 换到 正确的位置上。
if (pivot < nums[left]) left--;
int t = nums[start];
nums[start] = nums[left];
nums[left] = t;
return left; //返回轴线的所在下标
}
}
计数排序
/**
* 计数排序 是一种 非比较排序:
* (时间复杂度 n+k) - (空间复杂度 n+k) 。
* 思想:
* 将待排序数据本身作为计数数组的下标,
* 计数数组的下标就是数据本身,该下标内的值就是该数据的总个数。
* 因此,计数排序适合于数据量大且数据范围有限的情况。
*/
public class 计数排序 {
private static void sort(int[] nums){
int[] count = new int[10];
for (int i = 0; i < nums.length; i++) {
count[nums[i]] ++;
}
for (int i = 0,j = 0; i < count.length ; i++) {
while (count[i]-- >0) nums[j++] = i;
}
}
public static void main(String[] args) {
int[] nums = {3,6,1,8,2,5,7,3,8,0,4,9,6,1};
sort(nums);
for(int i = 0; i < nums.length; i++) {
System.out.print(nums[i]+"\t");
}
}
}