算法分析-线性时间排序,决策树,计数排序,基数排序,桶排序【线性排序】

决策树引导

      通俗来说,决策树分类的思想类似于找对象。现想象一个女孩的母亲要给这个女孩介绍男朋友,于是有了下面的对话:

      女儿:多大年纪了?

      母亲:26。

      女儿:长的帅不帅?

      母亲:挺帅的。

      女儿:收入高不?

      母亲:不算很高,中等情况。

      女儿:是公务员不?

      母亲:是,在税务局上班呢。

      女儿:那好,我去见见。

      这个女孩的决策过程就是典型的分类树决策。相当于通过年龄、长相、收入和是否公务员对将男人分为两个类别:见和不见。假设这个女孩对男人的要求是:30岁以下、长相中等以上并且是高收入者或中等以上收入的公务员,那么这个可以用下图表示女孩的决策逻辑(声明:此决策树纯属为了写文章而YY的产物,没有任何根据,也不代表任何女孩的择偶倾向,请各位女同胞莫质问我^_^):

      上图完整表达了这个女孩决定是否见一个约会对象的策略,其中绿色节点表示判断条件,橙色节点表示决策结果,箭头表示在一个判断条件在不同情况下的决策路径,图中红色箭头表示了上面例子中女孩的决策过程。

      这幅图基本可以算是一颗决策树,说它“基本可以算”是因为图中的判定条件没有量化,如收入高中低等等,还不能算是严格意义上的决策树,如果将所有条件量化,则就变成真正的决策树了。

      有了上面直观的认识,我们可以正式定义决策树了:

 

      决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。

      可以看到,决策树的决策过程非常直观,容易被人理解。目前决策树已经成功运用于医学、制造产业、天文学、分支生物学以及商业等诸多领域。知道了决策树的定义以及其应用方法,下面介绍决策树的构造算法。

下面给出A[1,2,3]排序的决策树,是算法导论的原图:

思考:

在一颗比较排序算法的决策树中,一个叶结点可能的最小深度是多少?

分析:

最少进行n-1次比较,所以深度最小是n-1

 

 

 

 

 

下面给出代码:

COUNTING-SORT 在数组 A={6,0,2,0,1,3,4,6,1,3,2}上的操作过程。

(1)C[1..k]被初始化为0。
(2)把等于A[j]的个数C[A[j]]进行循环累加 C[6]=1+1=2 C[0]=1+1=2 C[2]=1+1=2 C[1]=1+1=2 C[3]=1+1=2 C[4]=1 
(3)把小于等于A[j]的个数C[A[j]]进行循环累加。
C[0]=2
C[1]=C[1]+C[0]=2+2=4
C[2]=C[2]+C[1]=2+4=6
C[3]=C[3]+C[2]=2+6=8
C[4]=C[4]+C[3]=1+8=9
C[5]=C[5]+C[4]=0+9=9
C[6]=C[6]+C[5]=2+9=11
(4)把A[j]放入到恰当的B[C[A[j]]]位置中去。
B[C[2]]=B[6]=2    C[2]--  C[2]=5
B[C[3]]=B[8]=3    C[3]--  C[3]=7
B[C[1]]=B[4]=1    C[1]--  C[1]=3
B[C[6]]=B[11]=6   C[6]--  C[6]=10
B[C[4]]=B[9]=4    C[4]--  C[4]=8
B[C[3]]=B[7]=3    C[3]--  C[3]=6
B[C[1]]=B[3]=1    C[1]--  C[1]=2
B[C[0]]=B[2]=0    C[0]--  C[0]=1
B[C[2]]=B[5]=2    C[2]--  C[2]=4
B[C[0]]=B[1]=0    C[0]--  C[0]=0
B[C[6]]=B[10]=6   C[6]--  C[6]=9  所以:B[10]={0,0,1,4,2,2,3,3,4,6,6}

  1 Array.prototype.swap = function (i, j) {
  2     var temp = this[j];
  3     this[j] = this[i];
  4     this[i] = temp;
  5 };
  6 
  7 Array.prototype.getMaxVal = function () {
  8     //可以用任意我们学过的排序方法写,我们学过冒泡,选择,归并,快排,堆排序。
  9     //当然这样我们只要取出最大值即可,不需要全部排序,但是为了学程序,我将给出我们已经学过的所有排序方法,并全部排序
 10     var r = this.length - 1;
 11     //   return this.BUBBLE_SORT()[r];
 12     //    return this.SELECT_SORT()[r];
 13     //    return this.QUICK_SORT(0, r)[r];
 14     //heap_size = r+1; return.this.HRAP_SORT()[r];
 15     // return this.MERGE_SORT(0,r)[r];
 16     return this.INSERT_SORT()[r];
 17 };
 18 //冒泡
 19 Array.prototype.BUBBLE_SORT = function () {
 20 
 21     //冒泡就是两两将换,
 22     for (var i = this.length - 1; i > 0; i--) {
 23         for (var j = 0; j < i; j++) {
 24             if (this[j] > this[j + 1]) {
 25                 this.swap(j, j + 1);
 26             }
 27         }
 28     }
 29     return this;
 30 };
 31 
 32 
 33 //选择排序
 34 
 35 Array.prototype.SELECT_SORT = function () {
 36     for (var i = 0; i < this.length - 1; i++) {
 37         var min = this[i];  //默认选择第一个最小;
 38         var index = i;  //记录最小值的坐标
 39 
 40         for (var j = i + 1; j < this.length; j++) {
 41             if (this[j] < min) {
 42                 min = this[j];
 43                 index = j;
 44             }
 45         }
 46         this.swap(index, i);
 47     }
 48     return this;
 49 };
 50 
 51 
 52 //随机算法-快排
 53 //思想就是分治,递归。
 54 //最主要的函数就是PARTITION,返回下标i,i的左边都比A[i]小,右边比他大;
 55 Array.prototype.PARTITION = function (p, r) {
 56     var i = p - 1;
 57     var x = this[r];
 58     for (var j = p; j <= r - 1; j++) {
 59         if (this[j] < x) {
 60             this.swap(++i, j);
 61 
 62         }
 63 
 64     }
 65     this.swap(++i, r);
 66     return i;
 67 };
 68 
 69 Array.prototype.RANDOM_PARTION = function (p, r) {
 70 
 71     var q = p + Math.round(Math.random() * (r - p));
 72     this.swap(q, r);
 73     return this.PARTITION(p, r);
 74 };
 75 
 76 Array.prototype.QUICK_SORT = function (p, r) {
 77     if (r > p) {
 78         var q = this.RANDOM_PARTION(p, r);
 79         arguments.callee.call(this, p, q - 1);
 80         arguments.callee.call(this, q + 1, r);
 81     }
 82     return this;
 83 };
 84 
 85 
 86 //堆排序
 87 //其核心函数在于MAX_HRAPTITY,总能让i位置是最大的。
 88 var heap_size = 0; //包含heap_size下标在内的,之后的下标都是排序好的。
 89 
 90 Array.prototype.HEAP_SORT = function () {
 91     this.BUILD_MAX_HEAP();
 92     while (heap_size > 1) {
 93         this.swap(0, --heap_size);
 94         this.MAX_HEAPTIFY(0);
 95     }
 96     heap_size = this.length; //最后复原
 97     return this;
 98 };
 99 
100 //生成最大堆
101 Array.prototype.BUILD_MAX_HEAP = function () {
102 
103     for (var i = Math.floor(this.length / 2) - 1; i >= 0; i--) {
104         this.MAX_HEAPTIFY(i);
105     }
106 };
107 
108 //生成以i为根节点,它为最大值
109 Array.prototype.MAX_HEAPTIFY = function (i) {
110     var largest = i;
111     var l = i * 2 + 1;
112     var r = i * 2 + 2;
113 
114     if (l < heap_size && this[l] > this[largest]) {
115 
116         largest = l;
117     }
118 
119     if (r < heap_size && this[r] > this[largest]) {
120 
121         largest = r;
122     }
123     if (largest !== i) {
124         this.swap(largest, i);
125         arguments.callee.call(this, largest);
126     }
127 };
128 
129 //heap_size = A.length;
130 //归并排序
131 //其核心思想是分治,递归
132 
133 Array.prototype.MERGE_SORT = function (start, end) {
134 
135     if (end > start) { //记住这里没有等号,不然会一直递归下去。
136         var middle = Math.floor((start + end) / 2);
137         arguments.callee.call(this, start, middle);
138         arguments.callee.call(this, middle + 1, end);
139         this.MEAGE(start, middle, end);
140     }
141     return this;
142 };
143 
144 Array.prototype.MEAGE = function (s, m, e) {
145     var A = this.slice(s, m + 1),
146         B = this.slice(m + 1, e + 1),
147         lenA = m + 1 - s,
148         lenB = e - m,
149         i = 0,
150         j = 0;
151 
152     while (i < lenA && j < lenB) {
153         A[i] < B[j] ? this[s++] = A[i++] : this[s++] = B[j++];
154     }
155     while (i < lenA) {
156         this[s++] = A[i++];
157     }
158     while (j < lenB) {
159         this[s++] = B[j++];
160     }
161 };
162 
163 
164 //插入排序
165 Array.prototype.INSERT_SORT = function () {
166     try {
167         if (this.length < 1) {
168             throw new Error("错误长度的数组");
169         }
170         for (var i = 1; i < this.length; i++) {
171             var j = i - 1,
172                 temp = this[i];
173             while (this[j] > temp && j >= 0) {
174                 this[j + 1] = this[j];
175                 j--;
176             }
177             this[j + 1] = temp;
178         }
179 
180     } catch (e) {
181         console.error(e);
182     } finally {
183         return this;
184     }
185 };
186 
187 //初始化数组全为0;
188 Array.prototype.setZero4arr = function (k) {
189     var B = [];
190     for (var i = 0; i <= k; i++) {
191         B[i] = 0;
192     }
193     return B;
194 };
195 
196 
197 var A = [6, 0, 2, 0, 1, 3, 4, 6, 1, 3, 2],
198     k = A.getMaxVal(),
199     B = A.setZero4arr(A.length - 1);  //最后放入
200 
201 //计数排序
202 Array.prototype.COUNT_SORT = function (max, B) {
203 
204     var C = this.setZero4arr(max);   //0...max
205 
206 
207     for (var i = 0; i < this.length; i++) {
208         C[this[i]]++;   //知道了每个数的个数
209     }
210 
211     //累加得到最后的结果  C[i]包含了<= i 的个数。
212     for (var j = 1; j < C.length; j++) {
213         C[j] += C[j - 1];
214     }
215     //最后填充B
216     for (var k = this.length - 1; k >= 0; k--) {
217         B[C[this[k]] - 1] = this[k];
218 
219         //由于每个元素可能都不相同,因此,每当将一个值A[k]放入数组B中的时候,都要减少C[A[k]]的值,
220         //这会使得下一个其值等于A[k]的输入元素(如果存在的话)直接进入输出数组B中A[k]的前一个位置。
221         C[this[k]]--;
222 
223     }
224     return B;
225 };
226 
227 //思考 这样写对吗?稳定吗?看下面的思考题。
228 /*Array.prototype.COUNTING_SORT = function (k, B) {
229 
230     var C = this.setZero4arr(k);
231 
232     for (var i = 0; i < this.length; i++) {
233         C[this[i]]++;
234     }
235     for (var j = 1; j < C.length; j++) {
236         C[j] += C[j - 1];
237     }
238 
239     for (var k = 0; k < this.length; k++) {
240         B[C[this[k]] - 1] = this[k];
241         C[this[k]]--;
242     }
243     return B;
244 
245 };*/
246 
247 
248 console.log(A.COUNTING_SORT(k, B));

 

8.2-2 试证明COUNTING-SORT是稳定的。
由8.2-1例子可以发现 开始时候B[6]=2,这个是数组靠后位置的2,因为第4个循环是从后往前循环的,所以经过多次j--后,到了B[5]=2 这是考前位置的2.所以由8.2-1例子可以看出,原数组A靠后相同的数放到了新数组B靠后的位置,而原数组A靠前的数放到了新数组B靠前的位置。所以COUNTING-SORT是稳定的。

8.2-3 假设我们在COUNTING-SORT的第10行循环开始部分,将代码改写为:10.for j=1 to A.length 试证明该算法仍然正确,它还稳定吗?
如果把第10行改成for j=1 to A.length ,颠倒顺序处理元素的顺序,那么还是8.2-1的例子,其中的第(4)个循环就要微调了,对于相同元素来说,原数组靠前的相同元素出现在新数组靠后的位置上,反之亦然。所以就不稳定了。

 8.2-4 设计一个算法,它能够对于任何给定的驾驭0到K之间的n个整数先进行预处理,然后再O(1)时间内回答输入的n个整数中有多少个落在区间[a..b]内你设计的算法预处理时间应为Θ(n+k).
在n个整数中,通过第(3)个循环,计算出小于a值得元素个数C[a-1],小于等于b值得元素个数C[b],两者差值就是在[a,b]区间上的元素个数。
设落在区间[a,b]上元素个数x=C[b]-C[a-1].

 

基数排序

基数排序不需要比较关键字的大小

它是根据关键字中各位的值,通过对排序的N个元素进行若干趟“分配”与“收集”来实现排序的。 

 

不妨通过一个具体的实例来展示一下,基数排序是如何进行的。 

设有一个初始序列为: R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。

我们知道,任何一个阿拉伯数,它的各个位数上的基数都是以0~9来表示的。

所以我们不妨把0~9视为10个桶。 

我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是0,将这个数存入编号为0的桶中。

 

                                   

分类后,我们在从各个桶中,将这些数按照从编号0到编号9的顺序依次将所有数取出来。

这时,得到的序列就是个位数上呈递增趋势的序列。 

按照个位数排序: {50, 30, 0, 100, 11, 2, 123, 543, 187, 49}。

接下来,可以对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列。

 

8.3-2 下面的排序算法中哪些是稳定的,插入排序,归并排序,堆排序和快速排序?给出一个能使任何排序算法都稳定的方法。
归并和插入排序是稳定的,因为他们不改变相同元素原有的顺序。堆排序和快速排序是不稳定的。如果想让以上4种排序都是稳定排序,那么我们保存原数组相同元素的下标,在进行比较排序时,不同元素肯定没有这个问题,而相同元素在原数组中较小的下标对应的值放入到前面,较大的放到后面。

 

 下面我给出基数排序的一个算法实现代码:

 1 //基数排序
 2 //他是基于计数排序的。
 3 
 4 Array.prototype.COUNTING_SORT_4_RDIEX_SORT = function (b, d) {
 5     var C = this.setZero4arr(9);  //计数排序第一步
 6     var B = this.setZero4arr(this.length - 1);
 7 
 8     for (var j = 0; j < this.length; j++) { //计数排序第二步
 9 
10         C[get_bit_val(this[j], b, d)]++;
11     }
12 
13     for (var k = 1; k < C.length; k++) { //计数排序第三步
14         C[k] += C[k - 1];
15     }
16 
17     for (var i = this.length - 1; i >= 0; i--) { //计数排序第四步
18         B[C[get_bit_val(this[i], b, d)] - 1] = this[i]; 
19         C[get_bit_val(this[i], b, d)]--;  //这一步很关键,如果遇到重复的,以前是往前插入了,不然会覆盖。
20     }
21     return B;
22 };
23 
24 //获得该数字第i位的数字,从个位开始取,如果没有这位的数,默认为0。
25 /*
26  *  x是传入的数
27  *  i是第几位
28  *  最大数的长度
29  * 
30  * */
31 function get_bit_val(x, i, d) {
32     x += "";
33     var temp = x.split("");
34     while (temp.length < d) {
35         temp.unshift(0); //往前填充0
36     }
37     return temp[i];
38 }
39 
40 
41 Array.prototype.RDIEX_SORT = function () {
42     var k = this.getMaxVal().toString();
43     var d = k.length;
44     var B = [];
45     for (var i = d - 1; i >= 0; i--) {
46         i == d - 1 ? B = this.COUNTING_SORT_4_RDIEX_SORT(i, d) : B = this.COUNTING_SORT_4_RDIEX_SORT.call(B, i, d);
47     }
48     return B;
49 };
50 
51 var A = [16, 22, 2, 323320, 13, 3, 2321, 45546, 5766, 7, 23];
52 console.log(A.RDIEX_SORT());

桶排序Bucket sort


补充说明三点

1,桶排序是稳定的

2,桶排序是常见排序里最快的一种,比快排还要快…大多数情况下

3,桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法

二.算法描述

        例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。

        下图表示出了桶排序作用于有10个数的输入数组上的操作过程。

 

                                             

 

 

 1 function getR2(x, y) {
 2     return x * x + y * y;
 3 }
 4 Array.prototype.BUCKET_SORT = function () {
 5     var n = this.length; //n个桶
 6     var d2 = 0;//点到(0,0)的距离的平方
 7     var num = Math.ceil(d2 * n); //放在第几个桶里。
 8     var B = []; //当放桶子用
 9     var result = [];
10     for (var i = 0; i < n; i++) {
11         B[i] = {val: [], next: null};
12     }
13 
14     for (var j = 0; j < n; j++) {
15         d2 = getR2(this[j].x, this[j].y);
16         num = Math.ceil(d2 * n);
17         if (num === 0) num++; //直接在原点直接设为第一个桶子里
18         B[num - 1].val.push(this[j]);
19     }
20 
21     for (var k = 0; k < n; k++) {
22         if (B[k].val.length == 0) {
23             continue;
24         }
25         B[k].val.INSERT_SORT();
26         // result.concat(B[k].val);
27         result = result.concat(B[k].val);
28     }
29     return result;
30 };
31 Array.prototype.INSERT_SORT = function () {
32     for (var i = 1; i < this.length; i++) {
33         var key = this[i];
34         var j = i - 1;
35         while (j >= 0 && this[j] > key) {
36             this[j + 1] = this[j];
37             j--;
38         }
39         this[j + 1] = this[i];
40     }
41     return this;
42 };
43 
44 var A = [{x: 0, y: 0}, {x: 0.21, y: 0.3}, {x: 1, y: 0}];
45 
46 console.log(A.BUCKET_SORT());

 

posted @ 2016-09-26 21:04  hdu胡恩超  阅读(1729)  评论(0编辑  收藏  举报