冒泡排序只会操做相邻的两个数据。每次冒泡操做都会对相邻的两个元素进行比较,看是否知足大小关系要求。若是不知足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工做。java
图片来源网络,侵权即删
public static void bubbleSort(int[] a, int n) { if (n <= 1) return; for (int i = 0; i < n; ++i) { // 若是没有数据交换,则提早退出,提早退出冒泡循环的标志位 boolean flag = false; for (int j = 0; j < n - i - 1; ++j) { if (a[j] > a[j+1]) { // 交换 int tmp = a[j]; a[j] = a[j+1]; a[j+1] = tmp; flag = true; // 表示有数据交换 } } if (!flag) break; // 没有数据交换,提早退出 } }
插入排序就像咱们玩棋牌同样,每次摸的牌咱们都会插入到合适的位置,保证咱们的牌是有序的,在这个过程当中咱们有一个比大小的操做,插入排序没咱们手动方便,插入排序除了比大小以外还须要元素移动。例如:须要将一个数据 a 插入到已排序区间时,须要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点以后,咱们还须要将插入点以后的元素顺序日后移动一位,这样才能腾出位置给元素 a 插入。面试
图片来源网络,侵权即删
// 插入排序,a 表示数组,n 表示数组大小 public static void insertionSort(int[] a, int n) { if (n <= 1) return; for (int i = 1; i < n; ++i) { int value = a[i]; int j = i - 1; // 查找插入的位置 for (; j >= 0; --j) { if (a[j] > value) { a[j + 1] = a[j]; // 数据移动 } else { break; } } a[j + 1] = value; // 插入数据 } }
选择排序的思想很简单,从数组的下标i=0
开始,在i+1
后面找出最小的一个元素与i
对应的值比大小,若是i+1
的值小于i
的值,则将这两个数进行交换,而后i++
后面重这种操做,直到整个数组排好序。算法
图片来源网络,侵权即删
public static void sort(int arr[],int n){ for( int i = 0;i < n ; i++ ){ int min = i;//最小元素的下标 for(int j = i + 1;j < n; j++ ){ if(arr[j] < arr[min]){ min = j;//找最小值 } } //交换位置 int temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } }
归并排序采用的是分而治之的思想,咱们将要排序的数组从中间分红两半,分别对先后两个数组进行排序,而后将接口合并,这样整个数据就有序了。编程
归并排序通常咱们采用递归的方式,将数组进行拆分到不可分为止,而后对不可拆分的数组进行排序,左右两半数组分别从第一个开始逐一比较大小,放入到新的临时数组中,当左右两半的值同样时,将左边的值先放入到数组中,最后判断左右两边是否有剩余,将剩余的数组加入到临时数组中,通过上面的操做后,整个数组都有序了,最后须要将临时数组的值拷贝回原数组中,整个归并排序就完成了。数组
咱们以数组40, 2, 11, 5, 15, 6, 90, 10
来演示归并排序的过程微信
/** * 递归 将数组分红两半,分别对先后两部分排序 * * @param nums 数组 * @param leftPtr 左半边开始下标 * @param rightPtr 右半边结束下标 */ public static void merge_sort(int[] nums, int leftPtr, int rightPtr) { if (leftPtr >= rightPtr) return; // 将数组分红两半 int mid = leftPtr + (rightPtr - leftPtr) / 2; // 左半边排序 merge_sort(nums, leftPtr, mid); // 右半边排序 merge_sort(nums, mid + 1, rightPtr); merge(nums, leftPtr, mid + 1, rightPtr); } /** * 将先后两半排好序的数组进行合并 * * @param nums * @param leftPtr 左半边开始下标值 * @param rightPtr 右半边开始下标值 * @param rightBound 左半边结束值 */ public static void merge(int[] nums, int leftPtr, int rightPtr, int rightBound) { // 新开辟临时排序数组 int[] sortNums = new int[rightBound - leftPtr + 1]; // 求出中间值 int mid = rightPtr - 1; // 前半部分数组起始下标 int i = leftPtr; // 后半部分起始下标 int j = rightPtr; // 临时排序数组的起始下标 int k = 0; // 左右两边分别逐一比较,将小的存入到临时数组 while (i <= mid && j <= rightBound) { sortNums[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++]; } // 判断左半边时候有剩下 while (i <= mid) sortNums[k++] = nums[i++]; // 判断右半边时候有剩下 while (j <= rightBound) sortNums[k++] = nums[j++]; // 将数组拷贝回nums for (int m = 0; m < sortNums.length; m++) nums[leftPtr + m] = sortNums[m]; }
快速排序也称作快排,快排跟归并排序同样也是利分治思想。快排分为单轴快排和双轴快排,由于双轴排序效率比单抽排序效率高,因此这篇主要讲的是双轴排序。快速排序每次从数组中随机选择一个元素做为pivot(通常状况下,选择数组的最后一个元素),而后从数组的第一个元素和分区值前一个元素开始进行查找,从第一个元素开始查找比分区值大的第一个数A
,从分区值前面的一个数往前开始查找,找到第一个比分区值小的数B
,将A
和B
交换位置,直到左边开始查找的下标大于右边开始查找的下标中止查找,而后将分区值与第一个大于分区值的数交换位置,这样分区值左边的数就所有小于分区值,右边的数所有大于分区值。继续遍历左右两边的数组,直到整个数组排好序。网络
咱们以数组40, 2, 11, 5, 15, 6, 90, 10
来演示快速排序的过程
选择数组最右边的值做为分区值,即黄颜色的数组10,创建两个下标索引,一个指向数组的第一个元素left
,一个指向分区值的前一位数组的元素right
,即图中用红色箭头标出来的地方。从左边开始找出第一个比分区值大的元素,从右边找出第一个比分区值小的元素,将这两个元素进行交换而且将坐标日后移,即图中的第二步和第三步,按照上面的规则继续查找,直到left
> right
为止。将分区值与left
所指的元素进行交换,这样分区值左边的数都是比分区值小的,右边的数都是被分区值大的。按照上面的规则继续遍历左右两边的数组,最后整个数组就排好序了。框架
// 递归 public static void sort(int[] nums, int leftBound, int rightBound) { if (leftBound >= rightBound) return; // 分区值的下标位置 int mid = partition(nums, leftBound, rightBound); // 左分区排序 sort(nums, leftBound, mid - 1); // 右分区排序 sort(nums, mid, rightBound); } // 分区 public static int partition(int[] nums, int leftBound, int rightBound) { // 分区点的值 int piovt = nums[rightBound]; // 左边下标 int left = leftBound; //右边起始下标 int right = rightBound - 1; while (left <= right) { // 找到第一个大于分区值的 while (left <= right && nums[left] <= piovt) left++; // 找到第一个小于分区值的 while (left <= right && nums[right] > piovt) right--; // 将左右两边的值进行交换 if (left < right) swap(nums, left, right); } // 将left的值与分区值交换位置 swap(nums, left, rightBound); return left; } /** * 数据交换 * * @param nums * @param i * @param k */ public static void swap(int[] nums, int i, int k) { int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; }
桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据按照范围分到几个桶里,每一个桶里的数据再单独进行排序。桶内排完序以后,再把每一个桶里的数据按照顺序依次取出,组成的序列就是有序的了。性能
咱们以数组90,85,63,34,42,12,10
来演示桶排序的过程ui
public static void sort(int[] nums) { //最大值 int maxValue = nums[0]; // 最小值 int minValue = nums[0]; int length = nums.length; /** * 求出最大最小值 */ for (int i = 1; i < length; i++) { if (nums[i] > maxValue) { maxValue = nums[i]; } else if (nums[i] < minValue) { minValue = nums[i]; } } //最大值和最小值的差, int diff = maxValue - minValue; //初始化桶列表 ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>(); for (int i = 0; i < length; i++) { bucketList.add(new ArrayList<Integer>()); } //桶之间的数值间距 float section = (float) diff / (float) (length - 1); //数据入桶 for (int i = 0; i < length; i++) { //当前数除以区间得出存放桶的位置 减1后得出桶的下标 int num = (int) (nums[i] / section) - 1; if (num < 0) { num = 0; } bucketList.get(num).add(nums[i]); } //桶内排序 for (int i = 0; i < bucketList.size(); i++) { //jdk内置的集合排序框架 Collections.sort(bucketList.get(i)); } //写入原数组 int index = 0; for (ArrayList<Integer> arrayList : bucketList) { for (int value : arrayList) { nums[index] = value; index++; } } }
计数排序实际上是桶排序的一种特殊状况,当要排序的 n 个数据,所处的范围并不大的时候,好比最大值是 k,咱们就能够把数据划分红 k 个桶。每一个桶内的数据值都是相同的,省掉了桶内排序的时间。好比腾讯面试题,不用数据交换,查询出你的成绩排名?考生的满分是 750 分,最小是 0 分,这个数据的范围很小,因此咱们能够分红 750 个桶,对应分数从 0 分到 750 分。根据考生的成绩,咱们将这 50 万考生划分到这 750 个桶里。桶内的数据都是分数相同的考生,因此并不须要再进行排序。咱们只须要依次扫描每一个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。
咱们以数组5,8,9,7,8,4,5,6,9,3,0,2,1
来演示计数排序的过程
/** * 计数排序 * * @param nums 待排序数组 * @param rangeCount 数组范围个数 * @param min 最小的个数 * @return */ public static int[] countingSort(int[] nums, int rangeCount, int min) { int[] result = new int[nums.length]; // 定义计数桶 int[] count = new int[rangeCount + min]; // 将数据添加到桶里 for (int i = 0; i < nums.length; i++) { count[nums[i]]++; } // 遍历桶 将数据写入到返回数组中 for (int i = min, j = 0; i < count.length; i++) { while (count[i]-- > 0) result[j++] = i; } return result; }
基数排序也是桶排序的一种,基数排序对要排序的数据是有要求的,须要能够分割出独立的“位”来比较,并且位之间有递进的关系,若是 a 数据的高位比 b 数据大,那剩下的低位就不用比较了,除此以外,每一位的数据范围不能太大,要能够用线性排序算法来排序。例如假设咱们有 10 万个手机号码,须要按照从小到大的顺序对电话号码排序,前面的快排、桶排序、计数排序均可以进行快速的排序,可是内存有限,不容许你作这样的操做,这是就可使用基数排序来解决这个问题。
咱们以数组2154,5896,356,201,698,412
来演示基数排序的过程
public static void radixSort(int[] nums){ // 记录数组的大小 int length = nums.length; //最大值 int numMax = nums[0]; for(int i = 0;i < length;i++){ if(nums[i] > numMax){ numMax = nums[i]; } } //当前排序位置 int location = 1; //桶列表 一个桶中会有多个不一样的元素 ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>(); //初始化一个空桶 for(int i = 0; i < 10; i++){ bucketList.add(new ArrayList<Integer>()); } while(true) { //求出每位数的最小值 int min = (int)Math.pow(10,(location - 1)); // 判断最大值是否小于每位数的最小值,小于就结束 if(numMax < min){ break; } //遍历数据,将数据写入桶 for(int i = 0; i < length; i++) { //计算余数 放入相应的桶 int number = ((nums[i] / min) % 10); bucketList.get(number).add(nums[i]); } //将数从桶中取回,从新组成数组 int k = 0; for (int i=0;i<10;i++){ int size = bucketList.get(i).size(); for(int j = 0;j < size;j ++){ nums[k++] = bucketList.get(i).get(j); } // 将桶清空,用于下一次排序 bucketList.get(i).clear(); } // 位数加一 location++; } }
以上就是编程中的基本排序算法,基本排序算法,在不少组件内部使用,若是对基础排序了解的话,对你阅读源码仍是有些帮助的,很是感受你看到这里,但愿这篇文章对你有所帮助。
打个小广告,欢迎扫码关注微信公众号:「平头哥的技术博文」,按期分享Java技术干货,让咱们一块儿进步。