算法与数据结构是分不开的,我们今天重点学习几种重要的排序算法。
冒泡排序
冒泡排序我们在前面已经重点讲解了,这里就不多做介绍了。详情请看《基础篇》——数组02。
选择排序
同上,详情请看《基础篇》——数组02。
快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。
【算法思想】
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。【算法原理】
一趟快速排序的算法是:
1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2. 以第一个数组元素作为基准值,赋值给key,即key=A[0];
3. 从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
5. 重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。【算法描述】
1.查找基准值(最低位作为基准)应在位置
public static int getMiddle(int[] numbers, int low,int high) { int temp = numbers[low]; //数组的第一个作为中轴 while(low < high) { while(low < high && numbers[high] > temp) { high--; } numbers[low] = numbers[high];//比中轴小的记录移到低端 while(low < high && numbers[low] < temp) { low++; } numbers[high] = numbers[low] ; //比中轴大的记录移到高端 } numbers[low] = temp ; //中轴记录到尾 return low ; // 返回中轴的位置 }
2.递归形式的分治排序算法
public static void quickSort(int[] numbers,int low,int high) { if(low < high) { int middle = getMiddle(numbers,low,high); //将numbers数组进行一分为二 quickSort(numbers, low, middle-1); //对低字段表进行递归排序 quickSort(numbers, middle+1, high); //对高字段表进行递归排序 } }
插入排序(直接插入)
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序。
【算法思想】
每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。【算法步骤】
⒈ 从第一个元素开始,该元素可以认为已经被排序
⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描
⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置
⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
⒌ 将新元素插入到下一位置中
⒍ 重复步骤2~5【算法描述】
public static void insertSort(int[] numbers) { int size = numbers.length; int temp = 0 ; int j = 0; for(int i = 0 ; i < size ; i++) { temp = numbers[i]; //假如temp比前面的值小,则将前面的值后移 for(j = i ; j > 0 && temp < numbers[j-1] ; j --) { numbers[j] = numbers[j-1]; } numbers[j] = temp; } }
希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
【算法思想】
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。【算法步骤】
1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2. 按增量序列个数k,对序列进行k趟排序;
3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。【算法描述】
public static void shellSort(int[] data) { int j = 0; int temp = 0; //每次将步长缩短为原来的一半 for (int increment = data.length / 2; increment > 0; increment /= 2) { for (int i = increment; i < data.length; i++) { temp = data[i]; for (j = i; j >= increment; j -= increment) { if(temp > data[j - increment])//如想从小到大排只需修改这里 { data[j] = data[j - increment]; } else { break; } } data[j] = temp; } } }
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
【算法思想】
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。【算法步骤】
归并操作的工作原理如下:
1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4. 重复步骤3直到某一指针超出序列尾
5. 将另一序列剩下的所有元素直接复制到合并序列尾【算法描述】
public static int[] mergeSort(int[] nums, int low, int high) { int mid = (low + high) / 2; if (low < high) { // 左边 mergeSort(nums, low, mid); // 右边 mergeSort(nums, mid + 1, high); // 左右归并 merge(nums, low, mid, high); } return nums; }
public static void merge(int[] nums, int low, int mid, int high) { int[] temp = new int[high - low + 1]; int i = low;// 左指针 int j = mid + 1;// 右指针 int k = 0; // 把较小的数先移到新数组中 while (i <= mid && j <= high) { if (nums[i] < nums[j]) { temp[k++] = nums[i++]; } else { temp[k++] = nums[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = nums[i++]; } // 把右边边剩余的数移入数组 while (j <= high) { temp[k++] = nums[j++]; } // 把新数组中的数覆盖nums数组 for (int k2 = 0; k2 < temp.length; k2++) { nums[k2 + low] = temp[k2]; } }
堆排序
【堆】
1. 堆中某个节点的值总是不大于或不小于其父节点的值;
2. 堆总是一棵完全二叉树(Complete Binary Tree)。【满二叉树和完全二叉树】
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树称为满二叉树。
如果除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点,这样的二叉树被称为完全二叉树。
【堆排序】
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。【算法思想】
1. 初始时把要排序的n个数看作是一棵顺序存储的完全二叉树,调整它们的存储顺序,使之成为一个堆。
2. 将堆顶元素输出,得到n 个元素中最小(最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆。
3. 输出堆顶元素,得到n 个元素中次小(或次大)的元素。依次类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。【算法描述】
建堆
public static void buildMaxHeap(int[] data, int lastIndex) { //从lastIndex处节点(最后一个节点)的父节点开始 for(int i=(lastIndex-1)/2;i>=0;i--) { //k保存正在判断的节点 int k=i; //如果当前k节点的子节点存在 while(k*2+1<=lastIndex){ //k节点的左子节点的索引 int biggerIndex=2*k+1; //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在 if(biggerIndex<lastIndex) { //若果右子节点的值较大 { //biggerIndex总是记录较大子节点的索引 biggerIndex++; } } //如果k节点的值小于其较大的子节点的值 if(data[k]<data[biggerIndex]){ //交换 swap(data,k,biggerIndex); //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值 k=biggerIndex; }else{ break; } } } } //交换 public static void swap(int[] data, int i, int j) { int tmp=data[i]; data[i]=data[j]; data[j]=tmp; }
排序
public static void heapSort(int[] arr) { //循环建堆 for(int i=0;i<arr.length;i++){ //建堆 buildMaxHeap(a,arrayLength-1-i); //交换堆顶和最后一个元素 swap(a,0,arrayLength-1-i); } }
基数排序
【算法原理】
基数排序(Radix Sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。【算法描述】
public int[] radixSort(int[] arr, int n) { // 基于桶排序的基数排序 // 确定排序的趟数,即排序数组中最大值为809时,趟数为3 int max = arr[0]; for (int i = 0; i < n; i++) { if (arr[i] > max) { max = arr[i]; } } // 算出max的位数 int time = 0; while (max > 0) { max /= 10; time++; } // 【桶】初始化十个链表作为桶,用户分配时暂存 ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>(); for (int i = 0; i < 10; i++) { ArrayList<Integer> Item = new ArrayList<Integer>(); list.add(Item); } // 进行time次分配和收集 for (int i = 0; i < time; i++) { // 分配元素,按照次序优先,从个位数开始 for (int j = 0; j < n; j++) { int index = arr[j]%(int)Math.pow(10, i+1)/(int) Math.pow(10, i); list.get(index).add(arr[j]); } // 收集元素,一个一个桶地收集 int count = 0; // 10个桶 for (int k = 0; k < 10; k++) { // 每个桶收集 if (list.get(k).size() > 0) { for (int a : list.get(k)) { arr[count] = a; count++; } // 清除数据,以便下次收集 list.get(k).clear(); } } } return arr; }