标签:复杂度 18C void ons ddl 子序列 数组越界 方案 分区操作
写了一下排序算法的一些实例,Java语言写的,从网上也是各种找,各种测试,整理了一下,方便学习极客时间专栏-数据结构与算法:作者是王争。
1 //二路归并排序中计算排序次数 2 public static int number = 0; 3 4 /** 5 * 冒泡排序的算法 关键是先排列出最大的数 依次从大到小进行排序 6 * 7 * 算法描述: 8 * 1.比较相邻的元素,如果第一个比第二个大,就交换它们两个 9 * 2.对每一个相邻的元素做同样的工作,从开始的第一队,到结束的最后一对,这样在最后的元素应该是最大的数 10 * 3.针对所有的元素重复以上步骤,除了最后一个 11 * 4.重复步骤1~3,知道排序完成 12 * 13 * @param arr 需要排序的数组元素,这里只针对整数,当然其他的也可以 14 * @return 15 */ 16 public int[] bubbleSort(int[] arr){ 17 18 int len = arr.length; 19 20 for(int i=0; i<len-1; i++){ 21 //其实 len-1-i 说明的是第二个循环每次不用全部循环 只需要循环还没有排列出顺序的数 22 for(int j=0; j<len-1-i; j++){ 23 //相邻两元素 两两对比 24 if(arr[j] > arr[j+1]){ 25 //元素交换 把大数放在后面 26 int temp = arr[j+1]; 27 arr[j+1] = arr[j]; 28 arr[j] = temp; 29 } 30 } 31 } 32 return arr; 33 } 34 35 /** 36 * 选择排序算法 是一种简单直观的算法,在未排序的序列中找到最小(大)数,存放在数列起始的位置, 37 * 然后在从剩余的数列中寻找最小(大),放在已排序数列的末尾 38 * 39 * 算法描述: 40 * 1.初始状态:无序区为R[1..n],有序区为空 41 * 2.第i(i=1,2,3,..,n)趟排序开始时,当前有序区和无序区分别为R[1..i-1]、R[i..n],该趟排序从当前无序区中选出关键字最小的记录 42 * R[k],将他和无序区的最后一个R交换,使R[1..i]和R[i+1..n-1]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区 43 * 3.n-1趟结束,数据有序化了 44 * 45 * @param arr 待排序的整数数组 46 * @return 排序完成 47 */ 48 public int[] selectionSort(int[] arr){ 49 50 int len = arr.length; 51 int minIndex; 52 int temp; 53 54 // arr len=10 最后一个下标即是9 55 for(int i=0; i<len-1; i++){ 56 minIndex = i; 57 //j<len 这个不会出现数组越界的问题的因为没有用到 arr[j+1] 58 for(int j=i+1; j<len; j++){ 59 //寻找为排序的数列中最小的数 60 if(arr[j] < arr[minIndex]){ 61 //将最小的数的索引保存 62 minIndex = j; 63 } 64 } 65 //将两个数交换位置,之后继续排序,直到排序完成 66 temp = arr[i]; 67 arr[i] = arr[minIndex]; 68 arr[minIndex] = temp; 69 } 70 return arr; 71 } 72 73 /** 74 * 插入排序算法 通过构建有序数列,对于未排序数据,在已排序的序列中从后向前扫描,找到相应的位置并插入 75 * 76 * 算法描述: 77 * 1.从第一个元素开始,该元素可以被认为已经被排序 78 * 2.取出下一个元素,在已经排序的序列中从后向前扫描 79 * 3.如果该元素(已排序)大于新元素,将该元素移到下一位置 80 * 4.重复步骤三,直到找到已排序的元素小于或者等于新元素的位置 81 * 5.将新元素插入到该位置后 82 * 6.重复步骤2~5 83 * 84 * 问题: 85 * 这段排序的程序有点难以理解!!!大概是明白了 86 * 87 * @param arr 待排序的整数数组 88 * @return 排序完成 89 */ 90 public int[] insertionSort(int[] arr){ 91 92 int len = arr.length; 93 int preIndex; 94 int current; 95 96 for(int i=1; i<len; i++){ 97 preIndex = i-1; 98 current = arr[i]; 99 100 //对于preIndex-- 当前的数和已排序数列的数逐次比较大小,在有序数列中找到preIndex下标的数(下标为preIndex的数小于当前数) 101 while(preIndex >= 0 && arr[preIndex] > current){ 102 arr[preIndex+1] = arr[preIndex]; 103 preIndex--; 104 } 105 //结束while循环则表示当前数 大于或者等于有序数列中下标为preIndex的一个数,则把当前数 放在这个数的后面 106 arr[preIndex+1] = current; 107 } 108 return arr; 109 } 110 111 /** 112 * 希尔排序 1959年,shell发明,第一个突破O(n*n)的排序算法,是简单插入排序的改进版,它会优先比较较远的元素,又叫缩小增量排序 113 * 114 * 算法描述:(先将整个待排序的记录序列分割成若干个子序列分别进行直接插入排序) 115 * 1.选择一个增量序列t1,t2,...,tk 其中ti>tj,tk=1; 116 * 2.按增量序列个数k,对序列进行K趟排序 117 * 3.每趟序列,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对子表进行直接插入排序, 118 * 仅增量因子为1时,整个序列最为一个表来处理,表长度即为整个序列的长度 119 * 120 * 问题:这个有点难以理解,关键是间隔序列的设定,还有就是时间空间复杂度为啥小于O(n*n) 121 * 122 * @param arr 待排序的整数数组 123 * @return 排序完成 124 */ 125 public int[] shellSort(int[] arr){ 126 127 // long startTime=System.currentTimeMillis(); //获取开始时间 128 // System.out.println("程序运行开始时间: "+startTime); 129 130 int len = arr.length; 131 int gap = 1; 132 int temp; 133 int j; 134 135 //动态定义间隔序列 136 while(gap < len/3){ 137 gap = gap * 3 + 1; 138 } 139 140 for(; gap>0; gap=(int) Math.floor(gap/3)){ 141 for(int i=gap; i<len; i++){ 142 temp = arr[i]; 143 for(j=i-gap; j>0 && arr[j]>temp; j-=gap){ 144 arr[j+gap] = arr[j]; 145 } 146 arr[j+gap] = temp; 147 } 148 } 149 return arr; 150 } 151 152 /** 153 * 二路归并排序的算法 归并排序是建立在归并操作上的一种排序算法 先使子序列有序,再使子序列段间有序 154 * 155 * 算法描述: 156 * 1.将长度为n的序列分成长度为2/n的两个子序列 157 * 2.对这两个子序列分别进行归并排序 158 * 3.将排序好的子序列合并成一个最终的合并序列 159 * 160 * 问题: 161 * 这个归并排序算法 对于奇数个数的数组 好像还是有点问题 162 * 163 * @param arr 待排序的数组序列 164 * @return 排序完成 165 */ 166 public void mergeSort(int[] arr){ 167 168 System.out.println("开始排序~~~"); 169 int len = arr.length - 1; 170 sort(arr, 0 , len-1); 171 } 172 173 private void sort(int[] arr, int left, int right){ 174 175 if(left >= right){ 176 return; 177 } 178 179 int mid = (left + right)/2; 180 System.out.println("middle的数是:>>>>>>>>>>>>"+mid); 181 //二路归并排序有两个sort 多路归并排序写多个sort就可以了 182 sort(arr, left, mid); 183 sort(arr, mid+1, right); 184 merge(arr, left, mid, right); 185 186 } 187 188 private void merge(int[] arr, int left, int mid, int right){ 189 190 int[] tmp = new int[arr.length]; 191 int r1 = mid + 1; 192 int tIndex = left; 193 int cIndex = left; 194 195 //逐个合并 196 while(left <= mid && r1 <= right){ 197 if(arr[left] < arr[r1]){ 198 tmp[tIndex++] = arr[left++]; 199 }else{ 200 tmp[tIndex++] = arr[r1++]; 201 } 202 } 203 204 //将左边剩余 的合并 205 while(left <= mid){ 206 tmp[tIndex++] = arr[left++]; 207 } 208 209 //将右边剩余的合并 210 while(r1 <= right){ 211 tmp[tIndex++] = arr[r1++]; 212 } 213 214 System.out.println("第"+(++number)+"趟排序:\t"); 215 //从临时数组拷贝到原数组 216 while(cIndex <= right){ 217 arr[cIndex] = tmp[cIndex]; 218 System.out.print(arr[cIndex]+"\t"); 219 cIndex++; 220 } 221 System.out.println(); 222 } 223 224 /** 225 * 快速排序算法:通过一趟排序将待排记录分隔成独立的两部分,其中一部分的关键字均比另一部分的关键字小, 226 * 则可分别对这两部分记录继续排序,从而使整个序列有序 227 * 228 * 算法描述:快速排序算法采用分治法 把一个串分成两个子串 229 * 1.从数列中挑出一个元素,成为“基准”(pivot) 230 * 2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边) 231 * 在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作 232 * 3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序 233 * 234 * @param arr 待排序的数组序列 235 * @return 排序完成 236 */ 237 public int[] quickSort(int[] arr, int left, int right){ 238 239 int partitionIndex; 240 241 if (left < right) { 242 partitionIndex = partition(arr, left, right); 243 quickSort(arr, left, partitionIndex-1); 244 quickSort(arr, partitionIndex+1, right); 245 } 246 return arr; 247 } 248 249 //快速排序的分区操作 250 private int partition(int[] arr, int left, int right) { 251 //分区操作 设定基准值(pivot) 252 int pivot = left; 253 int index = pivot + 1; 254 for (int i = index; i <= right; i++) { 255 if (arr[i] < arr[pivot]) { 256 int temp = arr[i]; 257 arr[i] = arr[index]; 258 arr[index] = temp; 259 index++; 260 } 261 } 262 int temp = arr[pivot]; 263 arr[pivot] = arr[index-1]; 264 arr[index-1] = temp; 265 return index-1; 266 } 267 268 /** 269 * 计数排序算法 270 * 计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 271 * 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 272 * 273 * 算法描述: 274 * 1.找出待排序的最大和最小的数 275 * 2.统计数组中的每个值为i的元素出现的次数,存入数组c的第i项 276 * 3.对所有的计数累加(从C的第一个元素开始,每一项和前一项相加) 277 * 4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将c(i)减1 278 * 279 * @param arr 待排序的数组 280 * @return 281 */ 282 public int[] countingSort(int[] arr){ 283 284 int cnum =0; 285 for(int i = 0 ; i<arr.length;i++){ 286 if(cnum < arr[i]) { 287 cnum = arr[i]; 288 } 289 } 290 int[] c = new int[cnum+1]; 291 int[] b = new int[arr.length]; 292 293 for(int i =0; i<c.length; i++){ 294 c[i]=0; 295 } 296 297 for(int i = 0;i<arr.length;i++) { 298 c[arr[i]]+=1; 299 } 300 301 for(int i =1;i<c.length;i++) { 302 c[i]+=c[i-1]; 303 } 304 305 for(int i = arr.length-1;i>=0;i--){ 306 b[c[arr[i]]-1]=arr[i]; 307 c[arr[i]]-=1; 308 } 309 return b; 310 } 311 312 /** 313 * 314 * @param arr 待排序的数组 315 * @param maxDigit 316 * @return 317 */ 318 public int[] radixSort(int[] A){ 319 int length = A.length; 320 321 // 定义每一轮的除数,1,10,100... 322 int divisor = 1; 323 324 // 定义了10个桶,以防每一位都一样全部放入一个桶中 325 int[][] bucket = new int[10][length]; 326 327 // 统计每个桶中实际存放的元素个数 328 int[] count = new int[10]; 329 330 // 获取元素中对应位上的数字,即装入那个桶 331 int digit; 332 333 // 经过4次装通操作,排序完成 334 for (int i = 1; i <= 3; i++) { 335 // 计算入桶 336 for (int temp : A) { 337 digit = (temp / divisor) % 10; 338 bucket[digit][count[digit]++] = temp; 339 } 340 // 被排序数组的下标 341 int k = 0; 342 // 从0到9号桶按照顺序取出 343 for (int b = 0; b < 10; b++) { 344 // 如果这个桶中没有元素放入,那么跳过 345 if (count[b] == 0) 346 continue; 347 for (int w = 0; w < count[b]; w++) { 348 A[k++] = bucket[b][w]; 349 } 350 // 桶中的元素已经全部取出,计数器归零 351 count[b] = 0; 352 } 353 divisor *= 10; 354 } 355 return A; 356 }
注释:写到最后越来越难理解,后面的几个算法,基本上都是拷贝的,但是大体的思路是可以理解的,对于时间、空间复杂度分析,还是比较混乱,但是基本上头脑中有了程序优化的一个概念,最重要的一点是二路归并算法的存在一些问题,对于数组的个数为奇数的,总有最后一个数,不参与排序,等跟着专栏学习,看看能不能解决吧,网上的好多方案都不合适。。。(对于堆排序算法和桶排序算法,没有写)
参考的博客有许多,把主要的贴出来吧,望作者见谅
https://www.cnblogs.com/onepixel/articles/7674659.html
这个作者写的有动图的讲解,很形象!
https://www.cnblogs.com/shudonghe/p/3302888.html
https://www.cnblogs.com/haozhengfei/p/29ba40edbf659f2dbc6b429c2818c594.html
下面是测试代码,转载注明出处,谢谢!!!
//测试代码贴出来吧 public static void main(String[] args) { Sort sort = new Sort(); int[] arr = {3,38,5,15,36,26,30,50,29,59,43,62,18}; //sort.bubbleSort(arr); //sort.selectionSort(arr); //sort.insertionSort(arr); //sort.shellSort(arr); //sort.mergeSort(arr); //sort.quickSort(arr, 0, arr.length-1); //int[] arr1 = sort.countingSort(arr); sort.radixSort(arr); for(int i=0; i < arr.length; i++){ System.out.println("第"+(i+1)+"个数是>>>>>>>>>>"+arr[i]); } }
标签:复杂度 18C void ons ddl 子序列 数组越界 方案 分区操作
原文地址:https://www.cnblogs.com/ssh-html/p/9761074.html