排序算法的实例

  写了一下排序算法的一些实例,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)+"趟排序:	");
215         //从临时数组拷贝到原数组
216         while(cIndex <= right){
217             arr[cIndex] = tmp[cIndex];
218             System.out.print(arr[cIndex]+"	");
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]);
        }
    }