﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-elva-随笔分类-数据结构</title><link>http://www.cppblog.com/elva/category/15274.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 02 Nov 2010 22:36:36 GMT</lastBuildDate><pubDate>Tue, 02 Nov 2010 22:36:36 GMT</pubDate><ttl>60</ttl><item><title>QuickSort</title><link>http://www.cppblog.com/elva/archive/2010/11/02/132155.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Tue, 02 Nov 2010 08:24:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2010/11/02/132155.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/132155.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2010/11/02/132155.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/132155.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/132155.html</trackback:ping><description><![CDATA[快速排序(QuickSort)
<div>1、算法思想 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略，通常称其为分治法(Divide-and-ConquerMethod)。 </div>
<div>（1）&nbsp;&nbsp; 分治法的基本思想 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　分治法的基本思想是：将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题，然后将这些子问题的解组合为原问题的解。 </div>
<div>（2）快速排序的基本思想 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　设当前待排序的无序区为R[low..high]，利用分治法可将快速排序的基本思想描述为： <br>①分解：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　在R[low..high]中任选一个记录作为基准(Pivot)，以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high]，并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key，右边的子区间中所有记录的关键字均大于等于pivot.key，而基准记录pivot则位于正确的位置(pivotpos)上，它无须参加后续的排序。 <br>&nbsp;&nbsp;&nbsp; 注意： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos])： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　R[low..pivotpos-1].keys&#8804;R[pivotpos].key&#8804;R[pivotpos+1..high].keys <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其中low&#8804;pivotpos&#8804;high。 <br>②求解：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　&nbsp;&nbsp; 通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。 <br>③组合：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　因为当 "求解 "步骤中的两个递归调用结束时，其左、右两个子区间已有序。对快速排序而言， "组合 "步骤无须做什么，可看作是空操作。 </div>
<div>2、快速排序算法QuickSort <br>&nbsp;&nbsp;&nbsp; void&nbsp;&nbsp; QuickSort(SeqList&nbsp;&nbsp; R，int&nbsp;&nbsp; low，int&nbsp;&nbsp; high) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp; //对R[low..high]快速排序 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; pivotpos；&nbsp;&nbsp; //划分后的基准记录的位置 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(low &lt;high){//仅当区间长度大于1时才须排序 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pivotpos=Partition(R，low，high)；&nbsp;&nbsp; //对R[low..high]做划分 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; QuickSort(R，low，pivotpos-1)；&nbsp;&nbsp; //对左区间递归排序 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; QuickSort(R，pivotpos+1，high)；&nbsp;&nbsp; //对右区间递归排序 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; //QuickSort </div>
<div>&nbsp;&nbsp;&nbsp; 注意： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　为排序整个文件，只须调用QuickSort(R，1，n)即可完成对R[l..n]的排序。&nbsp;&nbsp; </div>
<div><br>上一页&nbsp;&nbsp; 下一页&nbsp;&nbsp; </div>
<div>3、划分算法Partition <br>（1）&nbsp;&nbsp; 简单的划分方法 <br>①&nbsp;&nbsp; 具体做法 <br>　　第一步：(初始化)设置两个指针i和j，它们的初值分别为区间的下界和上界，即i=low，i=high；选取无序区的第一个记录R[i](即R[low])作为基准记录，并将它保存在变量pivot中； <br>　　第二步：令j自high起向左扫描，直到找到第1个关键字小于pivot.key的记录R[j]，将R[j])移至i所指的位置上，这相当于R[j]和基准R[i](即pivot)进行了交换，使关键字小于基准关键字pivot.key的记录移到了基准的左边，交换后R[j]中相当于是pivot；然后，令i指针自i+1位置开始向右扫描，直至找到第1个关键字大于pivot.key的记录R[i]，将R[i]移到i所指的位置上，这相当于交换了R[i]和基准R[j]，使关键字大于基准关键字的记录移到了基准的右边，交换后R[i]中又相当于存放了pivot；接着令指针j自位置j-1开始向左扫描，如此交替改变扫描方向，从两端各自往中间靠拢，直至i=j时，i便是基准pivot最终的位置，将pivot放在此位置上就完成了一次划分。 </div>
<div>②一次划分过程 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　一次划分过程中，具体变化情况【参见动画演示】&nbsp;&nbsp; </div>
<div>③划分算法： <br>&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; Partition(SeqList&nbsp;&nbsp; R，int&nbsp;&nbsp; i，int&nbsp;&nbsp; j) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {//调用Partition(R，low，high)时，对R[low..high]做划分， <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //并返回基准记录的位置 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ReceType&nbsp;&nbsp; pivot=R[i]；&nbsp;&nbsp; //用区间的第1个记录作为基准&nbsp;&nbsp; ' <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while(i &lt;j){&nbsp;&nbsp; //从区间两端交替向中间扫描，直至i=j为止 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while(i &lt;j&amp;&amp;R[j].key&gt; =pivot.key)&nbsp;&nbsp; //pivot相当于在位置i上 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j--；&nbsp;&nbsp; //从右向左扫描，查找第1个关键字小于pivot.key的记录R[j] <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(i &lt;j)&nbsp;&nbsp; //表示找到的R[j]的关键字 &lt;pivot.key <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; R[i++]=R[j]；&nbsp;&nbsp; //相当于交换R[i]和R[j]，交换后i指针加1 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while(i &lt;j&amp;&amp;R[i].key &lt;=pivot.key)&nbsp;&nbsp; //pivot相当于在位置j上 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++；&nbsp;&nbsp; //从左向右扫描，查找第1个关键字大于pivot.key的记录R[i] <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(i &lt;j)&nbsp;&nbsp; //表示找到了R[i]，使R[i].key&gt; pivot.key <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; R[j--]=R[i];&nbsp;&nbsp; //相当于交换R[i]和R[j]，交换后j指针减1 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; //endwhile <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; R[i]=pivot；&nbsp;&nbsp; //基准记录已被最后定位 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp;&nbsp; i； <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; //partition&nbsp;&nbsp; </div>
<div><br>4、快速排序执行过程 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　快速排序执行的全过程可用递归树来描述。 <br>（图省略） <br>分析：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　（1）递归执行的路线如图中带箭头的包络线所示。 <br>　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （2）&nbsp;&nbsp; 递归树上每一结点左旁方括号表示当前待排序的区间，结点内的关键字是划分的基准关键字 <br>&nbsp;&nbsp;&nbsp; 注意： <br>　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 叶结点对应的子区间只有一个关键字，无须划分，故叶结点内没有基准关键字 <br>　　（3）&nbsp;&nbsp; 划分后得到的左、右两个子区间分别标在该结点的左、右两个孩子结点的左边方括号内。 <br>【例】根结点左旁方括号[49，38，65，97，76，13，27，49]表示初始待排序的关键字，根内的49表示所选的划分基准记录的关键字，划分结果是[27，28，13]49[76，97，65，49_]，其左右子区间分别标在根结点的两个孩子的左边。 <br>　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （4）&nbsp;&nbsp; 每个分支结点右旁圆括号中的内容表示对该结点左旁区间的排序过程结束之后返回的结果。它是其左右孩子对应的区间排序完成之后，将左右孩子对应的排序结果分别放在该分支结点的关键字前后所得到的关键字序列。 <br>【例】分支结点76的左右孩子对应的区间排序后的结果分别是(49_，65)和(97)，将它们分别放在76的前后即得(49，65，76，97)，这是对结点76左旁区间[76，97，，65，49]排序的结果。 <br>　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （5）&nbsp;&nbsp; 算法的执行顺序是递归树中的箭头顺序，实际上当把划分操作视为访问结点的操作时，快速排序的执行过程相当于是先序遍历其递归树。 <br>&nbsp;&nbsp;&nbsp; 注意： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　任何递归算法均可用递归树来描述其执行过程。 </div>
<div>5、快速排序各次划分后的状态变化 <br>[49&nbsp;&nbsp; 38&nbsp;&nbsp; 65&nbsp;&nbsp; 97&nbsp;&nbsp; 76&nbsp;&nbsp; 13&nbsp;&nbsp; 27&nbsp;&nbsp; 49]&nbsp;&nbsp; //初始关键字 <br>[27&nbsp;&nbsp; 38&nbsp;&nbsp; 13]&nbsp;&nbsp; 49&nbsp;&nbsp; [76&nbsp;&nbsp; 97&nbsp;&nbsp; 65&nbsp;&nbsp; 49]&nbsp;&nbsp; //第1次划分完成之后，对应递归树第2层 <br>[13]&nbsp;&nbsp; 27&nbsp;&nbsp; [38]&nbsp;&nbsp; 49&nbsp;&nbsp; [49&nbsp;&nbsp; 65]&nbsp;&nbsp; 76&nbsp;&nbsp; [97]&nbsp;&nbsp; //对上一层各无序区划分完成后，对应递归树第3层 <br>13&nbsp;&nbsp; 27&nbsp;&nbsp; 38&nbsp;&nbsp; 49&nbsp;&nbsp; 49&nbsp;&nbsp; [65]&nbsp;&nbsp; 76&nbsp;&nbsp; 97&nbsp;&nbsp; //对上一层各无序区划分完成后，对应递归树第4层 <br>13&nbsp;&nbsp; 27&nbsp;&nbsp; 38&nbsp;&nbsp; 49&nbsp;&nbsp; 49&nbsp;&nbsp; 65&nbsp;&nbsp; 76&nbsp;&nbsp; 97&nbsp;&nbsp; //最后的排序结果 </div>
<div>6、算法分析 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　快速排序的时间主要耗费在划分操作上，对长度为k的区间进行划分，共需k-1次关键字的比较。 </div>
<div>（1）最坏时间复杂度 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录，划分的结果是基准左边的子区间为空(或右边的子区间为空)，而划分所得的另一个非空的子区间中记录数目，仅仅比划分前的无序区中记录个数减少一个。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　因此，快速排序必须做n-1次划分，第i次划分开始时区间长度为n-i+1，所需的比较次数为n-i(1&#8804;i&#8804;n-1)，故总的比较次数达到最大值： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cmax&nbsp;&nbsp; =&nbsp;&nbsp; n(n-1)/2=O(n2) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　如果按上面给出的划分算法，每次取当前无序区的第1个记录为基准，那么当文件的记录已按递增序(或递减序)排列时，每次划分所取的基准就是当前无序区中关键字最小(或最大)的记录，则快速排序所需的比较次数反而最多。 </div>
<div>（2）&nbsp;&nbsp; 最好时间复杂度 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　在最好情况下，每次划分所取的基准都是当前无序区的 "中值 "记录，划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0(nlgn) <br>注意： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　用递归树来分析最好情况下的比较次数更简单。因为每次划分后左、右子区间长度大致相等，故递归树的高度为O(lgn)，而递归树每一层上各结点所对应的划分过程中所需要的关键字比较次数总和不超过n，故整个排序过程所需要的关键字比较总次数C(n)=O(nlgn)。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　因为快速排序的记录移动次数不大于比较的次数，所以快速排序的最坏时间复杂度应为0(n2)，最好时间复杂度为O(nlgn)。 </div>
<div>（3）基准关键字的选取 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　在当前无序区中选取划分的基准关键字是决定算法性能的关键。 <br>　　① "三者取中 "的规则 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　 "三者取中 "规则，即在当前区间里，将该区间首、尾和中间位置上的关键字比较，取三者之中值所对应的记录作为基准，在划分开始前将该基准记录和该区伺的第1个记录进行交换，此后的划分过程与上面所给的Partition算法完全相同。 </div>
<div>　　②取位于low和high之间的随机数k(low&#8804;k&#8804;high)，用R[k]作为基准 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　选取基准最好的方法是用一个随机函数产生一个取位于low和high之间的随机数k(low&#8804;k&#8804;high)，用R[k]作为基准，这相当于强迫R[low..high]中的记录是随机分布的。用此方法所得到的快速排序一般称为随机的快速排序。具体算法【参见教材】 <br>注意： <br>　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 随机化的快速排序与一般的快速排序算法差别很小。但随机化后，算法的性能大大地提高了，尤其是对初始有序的文件，一般不可能导致最坏情况的发生。算法的随机化不仅仅适用于快速排序，也适用于其它需要数据随机分布的算法。 </div>
<div>（4）平均时间复杂度 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　尽管快速排序的最坏时间为O(n2)，但就平均性能而言，它是基于关键字比较的内部排序算法中速度最快者，快速排序亦因此而得名。它的平均时间复杂度为O(nlgn)。 </div>
<div>（5）空间复杂度 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　快速排序在系统内部需要一个栈来实现递归。若每次划分较为均匀，则其递归树的高度为O(lgn)，故递归后需栈空间为O(lgn)。最坏情况下，递归树的高度为O(n)，所需的栈空间为O(n)。 </div>
<div>（6）稳定性 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　快速排序是非稳定的，例如[2，2，1]。 </div>
<br><br>转自：<br><a href="http://www.360doc.com/content/10/1025/15/4161063_63868950.shtml">http://www.360doc.com/content/10/1025/15/4161063_63868950.shtml</a>
<img src ="http://www.cppblog.com/elva/aggbug/132155.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2010-11-02 16:24 <a href="http://www.cppblog.com/elva/archive/2010/11/02/132155.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shellsort之二</title><link>http://www.cppblog.com/elva/archive/2010/11/01/132021.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Mon, 01 Nov 2010 10:08:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2010/11/01/132021.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/132021.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2010/11/01/132021.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/132021.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/132021.html</trackback:ping><description><![CDATA[转自：<br><a href="http://blog.sina.com.cn/s/blog_61e439e50100mfe8.html">http://blog.sina.com.cn/s/blog_61e439e50100mfe8.html</a><br><br>
<p style="TEXT-INDENT: 21pt"><a name=OLE_LINK2></a><a name=OLE_LINK1><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">希尔排序</font></span><span lang=EN-US XML:LANG="EN-US">(shellsort)</span></span></a><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">又叫增量递减</font></span><span lang=EN-US XML:LANG="EN-US">(diminishing increment)</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">排序，是由</font></span><span lang=EN-US XML:LANG="EN-US">D.L. Shell</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">发明的，这个算法是通过一个逐渐减小的增量使一个数组逐渐趋近于有序从而达到排序的目的，该算法由</font></span><span lang=EN-US XML:LANG="EN-US">1959</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">年公布。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">最差时间复杂度：根据步长序列的不同而不同。</font></span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">已知最好的</font></span><span lang=EN-US XML:LANG="EN-US">: <span><em>O</em>(<em>n</em>log<sup><font size=2>2</font></sup><em>n</em>)</span></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">最优时间复杂度：</font></span><span lang=EN-US XML:LANG="EN-US">O(<em>n</em>)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">平均时间复杂度：根据步长序列的不同而不同。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">原始的算法实现在最坏的情况下需要进行</font></span><span lang=EN-US XML:LANG="EN-US">O(n2)</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">的比较和交换。</font></span><span lang=EN-US XML:LANG="EN-US">V. Pratt</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">的书</font></span><span lang=EN-US XML:LANG="EN-US">[1]</span></span></span> <span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">对算法进行了少量修改，可以使得性能提升至</font></span><span lang=EN-US XML:LANG="EN-US">O(n log2 n)</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">。这比最好的比较算法的</font></span><span lang=EN-US XML:LANG="EN-US">O(n log n)</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">要差一些。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越大的步长进行排序，算法的最后一步就是普通的插入排序，但是到了这步，需排序的数据几乎是已排好的了（此时插入排序较快）。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为</font></span><span lang=EN-US XML:LANG="EN-US">O(n2)</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">的排序（冒泡排序或插入排序），可能会进行</font></span><span lang=EN-US XML:LANG="EN-US">n</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据，所以小数据只需进行少数比较和交换即可到正确位置。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">一个更好理解的希尔排序实现：将数组列在一个表中并对列排序（用插入排序）。重复这过程，不过每次用更长的列来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法，算法本身仅仅对原数组进行排序（通过增加索引的步长，例如是用</font></span><span lang=EN-US XML:LANG="EN-US">i += step_size</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">而不是</font></span><span lang=EN-US XML:LANG="EN-US">i++</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">）。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">例如，假设有这样一组数</font></span><span lang=EN-US XML:LANG="EN-US">[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">，如果我们以步长为</font></span><span lang=EN-US XML:LANG="EN-US">5</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">开始进行排序，我们可以通过将这列表放在有</font></span><span lang=EN-US XML:LANG="EN-US">5</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">行的表中来更好地描述算法，这样他们就应该看起来是这样：</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">13 14 94 33 82</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">25 59 94 65 23</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">45 27 73 25 39</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">10</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">然后我们对每行进行排序：</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">10 14 73 25 23</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">13 27 94 33 39</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">25 59 94 65 82</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">45</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">当我们以单行来读取数据时我们得到：</font></span><span lang=EN-US XML:LANG="EN-US">[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">这时</font></span><span lang=EN-US XML:LANG="EN-US">10</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">已经移至正确位置了，然后再以</font></span><span lang=EN-US XML:LANG="EN-US">3</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">为步长进行排序：</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">10 14 73</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">25 23 13</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">27 94 33</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">39 25 59</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">94 65 82</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">45</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">排序之后变为：</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">10 14 13</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">25 23 33</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">27 25 59</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">39 65 73</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">45 94 82</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">94</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">最后以</font></span><span lang=EN-US XML:LANG="EN-US">1</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">步长进行排序（此时就是简单的插入排序了）。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">步长的选择是希尔排序的重要部分。只要最终步长为</font></span><span lang=EN-US XML:LANG="EN-US">1</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序，最终算法以步长为</font></span><span lang=EN-US XML:LANG="EN-US">1</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">进行排序。当步长为</font></span><span lang=EN-US XML:LANG="EN-US">1</span></span></span><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">时，算法变为插入排序，这就保证了数据一定会被排序。</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span style="FONT-FAMILY: '微软雅黑', 'sans-serif'"><font face="">算法如下</font></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">#include &lt;stdio.h&gt;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">&nbsp;<wbr></span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">void output_array(int data[], int n)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">{</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> int i;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> for(i = 0; i &lt; n; i++)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> printf("%d ", data[i]);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> printf("\n");</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">}</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">void swap(int *a, int *b)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">{</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> int x;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> x = *a;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> *a = *b;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> *b = x;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">}</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">void insertion_sort(int data[], int n, int increment)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">{</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> int i, j;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> for(i = increment; i &lt; n; i += increment)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> for(j = i; j &gt;= increment &amp;&amp; data[j] &gt; data[j - increment]; j -= increment)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> swap(&amp;data[j], &amp;data[j - increment]);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">}</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">void shellsort(int data[], int n)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">{</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> int i, j;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> for(i = n / 2; i &gt; 2; i /= 2)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> for(j = 0; j &lt; i; j++)</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> <span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span>insertion_sort(data + j, n - j, i);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> insertion_sort(data, n, 1);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">}</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">int main()</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">{</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> int data[] = {5, 3, 1, 665, 77, 66, 44, 11, 10, 9, 8, 6};</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> output_array(data, 12);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> shellsort(data, 12);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> output_array(data, 12);</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> return 0;</span></span></span></p>
<p style="TEXT-INDENT: 21pt"><span><span><span lang=EN-US XML:LANG="EN-US">}</span></span></span></p>
<img src ="http://www.cppblog.com/elva/aggbug/132021.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2010-11-01 18:08 <a href="http://www.cppblog.com/elva/archive/2010/11/01/132021.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shellsort之三</title><link>http://www.cppblog.com/elva/archive/2010/11/01/132022.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Mon, 01 Nov 2010 10:08:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2010/11/01/132022.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/132022.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2010/11/01/132022.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/132022.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/132022.html</trackback:ping><description><![CDATA[<br>网站: <a href="http://www.javaeye.com/"><u><font color=#0066cc>JavaEye</font></u></a>&nbsp; 作者: <a href="http://shenyu.javaeye.com/"><u><font color=#0066cc>shenyu</font></u></a>&nbsp; 链接： <a style="COLOR: red" href="http://shenyu.javaeye.com/blog/189563"><u>http://shenyu.javaeye.com/blog/189563</u></a>&nbsp; 发表时间: 2008年05月05日 <br><br>声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！ <br><br>
<p><a href="http://www.cppblog.com/elva/admin/188974"><u><font color=#0066cc>插入排序</font></u></a> 对基本有序的数组效果非常好，但是对于通常情况则表现一般。假设最小的数字在最右边，升序排序时，这个数则要经过n次交换比较换到最左边。希尔排序则是对插入排序的很好的修正。而且在希尔排序很少出现最坏状况。</p>
<p>希尔排序通过对数组 以一定间隔相隔的位置 进行插入排序，以达到让数据快速出现在它应该出现的位置的周围，使数组逐步接近基本有序。随着间隔的减少，数组越来越接近基本有序，最后间隔为1时，变成标准的插入排序。</p>
<p>数据的间隔有多种算法，一般要求间隔序列之间互质，此处使用Kunth序列：h = h * 3 + 1</p>
<p>希尔排序的时间效率很难从理论上证明，实验表明大约是O(n^(3/2)) ~ O(n^(7/6))之间。</p>
<p>代码如下：</p>
<pre class=java name="code">class Shell {
public static void main(String[] args) {
int[] a = {9,8,7,6,5,4,3,2,1};
sort(a);
println(a);
}
private static void println(int[] a) {
for(int i: a) System.out.print(i + " ");
System.out.println();
}
private static void sort(int[] a) {
int h = 1;
while(h &lt;= a.length/3) h = h * 3 + 1;	//产成Kunth序列
while(h &gt; 0) {
for(int i = h; i &lt; a.length; i++) {	//对每个数据进行间隔为h的插入排序
int pos = i;
int temp = a[i];
while(pos &gt;= h &amp;&amp; a[pos - h] &gt; temp) {
a[pos] = a[pos-h];
pos -= h;
}
a[pos] = temp;
}
h = (h - 1) / 3;	//减小间隔值
}
}
}</pre>
&nbsp; 
<img src ="http://www.cppblog.com/elva/aggbug/132022.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2010-11-01 18:08 <a href="http://www.cppblog.com/elva/archive/2010/11/01/132022.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shellsort之一</title><link>http://www.cppblog.com/elva/archive/2010/11/01/132020.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Mon, 01 Nov 2010 10:06:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2010/11/01/132020.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/132020.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2010/11/01/132020.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/132020.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/132020.html</trackback:ping><description><![CDATA[转自：<br><a href="http://apps.hi.baidu.com/share/detail/15570437">http://apps.hi.baidu.com/share/detail/15570437</a><br><br><br>
<div id=app-share-content>
<p><strong>基本思想</strong></p>
<p>先取一个小于n的整数d1作为第一个增量，把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序；然后，取第二个增量d2&lt;d1重复上述的分组和排序，直至所取的增量dt=1(dt&lt;dt-l&lt;&#8230;&lt;d2&lt;d1)，即所有记录放在同一组中进行直接插入排序为止。</p>
<p><strong>算法实现(Java语言)</strong></p>
<p>package org.shirdrn.internal.sort;</p>
<p>/**<br>* &lt;p&gt;&lt;B&gt;希尔排序算法类&lt;/B&gt;<br>* &lt;p&gt;基本思想：<br>* &lt;p&gt;<br>* &lt;p&gt;先取一个小于n的整数d1作为第一个增量，把文件的全部记录分成d1个组。所有距离为dl的<br>* 倍数的记录放在同一个组中。先在各组内进行直接插人排序；然后，取第二个增量d2&lt;d1重复上<br>* 述的分组和排序，直至所取的增量dt=1(dt&lt;dt-l&lt;&#8230;&lt;d2&lt;d1)，即所有记录放在同一组中进行<br>* 直接插入排序为止。<br>* &lt;p&gt;<br>* &lt;p&gt;该方法实质上是一种分组插入方法。<br>* <br>* @author shirdrn<br>*<br>*/<br>public class ShellSort {</p>
<p>private Integer[] array;<br><br>public ShellSort(Integer[] array) {<br>&nbsp;&nbsp; this.array = array;<br>}<br><br>public void sort() {<br>&nbsp;&nbsp; int d = array.length;<br>&nbsp;&nbsp; do {<br>&nbsp;&nbsp;&nbsp; d /= 2;<br>&nbsp;&nbsp;&nbsp; shellPass(d); // 根据逐渐减小的间隔增量，循环调用一趟排序<br>&nbsp;&nbsp; }while(d&gt;1);<br>}<br><br>/**<br>* 希尔一趟排序<br>* <br>* @param d 间隔增量<br>*/<br>private void shellPass(int d) {<br>&nbsp;&nbsp; Integer tmp;<br>&nbsp;&nbsp; for(int i=d; i&lt;array.length; i++) { // 数组下标从0开始，初始i=d表示一趟排序中第二个元素<br>&nbsp;&nbsp;&nbsp; tmp = array[i]; // array[i]的拷贝<br>&nbsp;&nbsp;&nbsp; // 如果待处理的无序区第一个元素array[i] &lt; 有序区最大的元素array[i-d]<br>&nbsp;&nbsp;&nbsp; // 需要将有序区比array[i]大的元素向后移动<br>&nbsp;&nbsp;&nbsp; if(array[i]&lt;array[i-d]) { <br>&nbsp;&nbsp;&nbsp;&nbsp; int j=i-d;<br>&nbsp;&nbsp;&nbsp;&nbsp; while(j&gt;=0 &amp;&amp; tmp&lt;array[j]) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; array[j+d] = array[j]; // 将左侧有序区中元素比array[i]大的array[j+d]后移<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j -= d;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; // 如果array[i] &gt;= 左侧有序区最大的array[i-d]，或者经过扫描移动后，找到一个比array[i]小的元素<br>&nbsp;&nbsp;&nbsp;&nbsp; // 将右侧无序区第一个元素tmp = array[i]放到正确的位置上<br>&nbsp;&nbsp;&nbsp;&nbsp; array[j+d] = tmp; <br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp; }<br>}<br><br>/**<br>* 输出数组元素<br>*/<br>public String print() {<br>&nbsp;&nbsp; StringBuffer sb = new StringBuffer();<br>&nbsp;&nbsp; for(int i=0; i&lt;array.length; i++) {<br>&nbsp;&nbsp;&nbsp; sb.append(array[i]);<br>&nbsp;&nbsp;&nbsp; if(i != array.length-1) {<br>&nbsp;&nbsp;&nbsp;&nbsp; sb.append(", ");<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp; }<br>&nbsp;&nbsp; return sb.toString();<br>}<br>}</p>
<p><strong>排序过程</strong></p>
<p>希尔排序的过程如下：</p>
<p>首先初始化间隔d为待排序数组的长度，无需排序。</p>
<p>减小d，对于每次得到的间隔d，执行多组排序，使得原始数组间隔为d的一个子数组为有序，该数组通过类似直接插入排序的算法来执行排序。</p>
<p>直到，d减小为1的时候，整个数组为有序。这里，采用二分的策略来得到间隔d。</p>
<p>执行希尔排序的过程示例如下：</p>
<p>假设待排序数组为array = {94,12,34,<font color=#ff00ff>76</font>,26,9,0,37,55,<font color=#ff0000>76</font>,37,5,68,83,90,37,12,65,<font color=#0000ff>76</font>,49}，数组大小为20。</p>
<p>首先，初始化d = 20。在循环中反复得到间隔d，根据d执行一趟希尔排序。</p>
<p>对于d = 20/2 = 10：</p>
<p>根据d = 10来对数组排序，将原始数组分成2块： {94,12,34,<font color=#ff00ff>76</font>,26,9,0,37,55,<font color=#ff0000>76</font>}与{37,5,68,83,90,37,12,65,<font color=#0000ff>76</font>,49}，也就是对如下数组分别进行直接插入排序：</p>
<p>{array[0],array[10]} = {94,37}</p>
<p>{array[1],array[11]} = {12,5}</p>
<p>{array[2],array[12]} = {34,68}</p>
<p>{array[3],array[13]} = {<font color=#ff00ff>76</font>,83}</p>
<p>{array[4],array[14]} = {26,90}</p>
<p>{array[5],array[15]} = {9,37}</p>
<p>{array[6],array[16]} = {0,12}</p>
<p>{array[7],array[17]} = {37,65}</p>
<p>{array[8],array[18]} = {55,<font color=#0000ff>76</font>}</p>
<p>{array[9],array[19]} = {<font color=#ff0000>76</font>,49}</p>
<p>第一趟希尔排序后，各个子数组变为：</p>
<p>{37,5,34,<font color=#ff00ff>76</font>,26,9,0,37,55,49}与{94,12,68,83,90,37,12,65,<font color=#0000ff>76</font>,<font color=#ff0000>76</font>}，</p>
<p>即：array = {37,5,34,<font color=#ff00ff>76</font>,26,9,0,37,55,49,94,12,68,83,90,37,12,65,<font color=#0000ff>76</font>,<font color=#ff0000>76</font>}，</p>
<p>对于d = 10/2 = 5：</p>
<p>根据d = 5来对数组排序，将第一趟希尔排序后的数组分成4块 ：{37,5,34,<font color=#ff00ff>76</font>,26}、{9,0,37,55,49}、{94,12,68,83,90}与{37,12,65,<font color=#0000ff>76</font>,<font color=#ff0000>76</font>}，也就是对如下数组分别进行直接插入排序：</p>
<p>{array[0],array[5],array[10],array[15]} = {37,9,94,37}</p>
<p>{array[1],array[6],array[11],array[16]} = {5,0,12,12}</p>
<p>{array[2],array[7],array[12],array[17]} = {34,37,68,65}</p>
<p>{array[3],array[8],array[13],array[18]} = {<font color=#ff00ff>76</font>,55,83,<font color=#0000ff>76</font>}</p>
<p>{array[4],array[9],array[14],array[19]} = {26,49,90,<font color=#ff0000>76</font>}</p>
<p>第二趟希尔排序后，各个子数组变为：</p>
<p>{9,0,34,55,26}、{37,5,37,<font color=#ff0000>76</font>,49}、{37,12,65,<font color=#ff00ff>76</font>,<font color=#0000ff>76</font>}与{94,12,68,83,90}，</p>
<p>即：array = {9,0,34,55,26,37,5,37,76,49,37,12,65,76,76,94,12,68,83,90}。</p>
<p>对于d = 5/2 = 2：</p>
<p>根据d = 2来对数组排序，将第二趟希尔排序后的数组分成10块： {9,0}、{34,55}、{26,37}、{5,37}、{<font color=#ff0000>76</font>,49}、{37,12}、{65,<font color=#ff00ff>76</font>}、{<font color=#0000ff>76</font>,94}、{12,68}与{83,90}，也就是对如下数组分别进行直接插入排序：</p>
<p>{array[0],array[2],array[4],array[6],array[8],array[10],array[12],array[14],array[16],array[18]} = {9,34,26,5,<font color=#ff0000>76</font>,37,65,<font color=#0000ff>76</font>,12,83}</p>
<p>{array[1],array[3],array[5],array[7],array[9],array[11],array[13],array[15],array[17],array[19]} = {0,55,37,37,49,12,<font color=#ff00ff>76</font>,94,68,90}</p>
<p>第三趟希尔排序后，各个子数组变为：{5,0}、{9,12}、{12,37}、{26,37}、{34,49}、{37,55}、{65,68}、{<font color=#ff0000>76</font>,<font color=#ff00ff>76</font>}、{<font color=#0000ff>76</font>,90}与{83,94}，</p>
<p>即：array = ：{5,0,9,12,12,37,26,37,34,49,37,55,65,68,<font color=#ff0000>76</font>,<font color=#ff00ff>76</font>,<font color=#0000ff>76</font>,90,83,94}。</p>
<p>对于d = 2/2 = 1：</p>
<p>根据d = 1来对数组排序，将第二趟希尔排序后的数组分成20块：{5}、{0}、{9}、{12}、{12}、{37}、{26}、{37}、{34}、{49}、{37}、{55}、{65}、{68}、{<font color=#ff0000>76</font>}、{<font color=#ff00ff>76</font>}、{<font color=#0000ff>76</font>}、{90}、{83}、{94}，也就是对如下数组分别进行直接插入排序：</p>
<p>{5,0,9,12,12,37,26,37,34,49,37,55,65,68,<font color=#ff0000>76</font>,<font color=#ff00ff>76</font>,<font color=#0000ff>76</font>,90,83,94}</p>
<p>第四趟希尔排序以后，数组已经有序：</p>
<p>array = {0,5,9,12,12,26,34,37,37,37,49,55,65,68,76,76,76,83,90,94}。</p>
<p>因为 d= 1，希尔排序结束。</p>
<p><strong>测试用例</strong></p>
<p>package org.shirdrn.internal.sort;</p>
<p>import junit.framework.TestCase;</p>
<p>public class TestShellSort extends TestCase {</p>
<p>private ShellSort sort;<br>private Integer[] array;<br><br>@Override<br>protected void setUp() throws Exception {<br>&nbsp;&nbsp; array = new Integer[]{<br>&nbsp;&nbsp;&nbsp;&nbsp; 94,12,34,76,26,9,0,37,55,76,37,5,68,83,90,37,12,65,76,49<br>&nbsp;&nbsp; };<br>&nbsp;&nbsp; sort = new ShellSort(array);<br>}<br><br>public void testSort() {<br>&nbsp;&nbsp; // B(Before),A(After)<br>&nbsp;&nbsp; System.out.println("(B)Sorting : " + this.sort.print());<br>&nbsp;&nbsp; this.sort.sort(); <br>&nbsp;&nbsp; System.out.println("(A)Sorting : " + this.sort.print());<br>}<br>}</p>
<p>测试结果：</p>
<p>(B)Sorting : 94, 12, 34, 76, 26, 9, 0, 37, 55, 76, 37, 5, 68, 83, 90, 37, 12, 65, 76, 49<br>(A)Sorting : 0, 5, 9, 12, 12, 26, 34, 37, 37, 37, 49, 55, 65, 68, 76, 76, 76, 83, 90, 94</p>
<p><strong>算法分析</strong></p>
<p>（一）时间复杂度</p>
<p>Shell排序的执行时间依赖于增量序列。</p>
<p>好的增量序列的共同特征：</p>
<p>① 最后一个增量必须为1；</p>
<p>② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。</p>
<p>有人通过大量的实验，给出了目前较好的结果：当n较大时，比较和移动的次数约在n<sup>l.25</sup>到1.6n<sup>1.25</sup>之间。</p>
<p>（二）空间复杂度</p>
<p>因为希尔排序依赖于增量序列，从而导致排序的趟数不固定，对于不同的增量执行一趟希尔排序，只用到一个辅助变量。</p>
<p>（三）排序稳定性</p>
<p>通过上述元素76可以看到，希尔排序不稳定。</p>
<p>因此，希尔排序是不稳定的。</p>
</div>
<img src ="http://www.cppblog.com/elva/aggbug/132020.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2010-11-01 18:06 <a href="http://www.cppblog.com/elva/archive/2010/11/01/132020.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>谈谈 Hash Table</title><link>http://www.cppblog.com/elva/archive/2010/10/25/131186.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Mon, 25 Oct 2010 04:57:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2010/10/25/131186.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/131186.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2010/10/25/131186.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/131186.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/131186.html</trackback:ping><description><![CDATA[转自：<br><a href="http://geeklu.com/2010/07/hash-table/">http://geeklu.com/2010/07/hash-table/</a><br><br><span id=.>&nbsp;
<h2>一.数据结构</h2>
</span>
<p>在我们编程的世界里数据的基本组织可以说有三种形式。</p>
<ol>
    <li>结构体(或对象)
    <li>数组
    <li>链表 </li>
</ol>
<p>其他任何的数据组织形式都可以看作是这三种数据组织形式的组合变体。<br><img class="alignnone size-medium wp-image-30874" title=find-people alt="" src="http://geeklu.com/wp-content/uploads/2010/07/find-people-300x225.jpg" width=300 height=225><br>结构体(或对象)可以是基本数据类型或者其他结构体(或对象)的组合。结构体或对象一般用来描述一个复杂数据实体。</p>
<p>数组一般是一组同类型的变量的集合，在内存中表现为一片连续的空间，因为空间是连续的，且每一个数据单元占的内存空间的大小是相等的，所以可以根据地址的偏移对数据元素实现快速访问，但是当需要插入或者删除一个元素的时候，则需要对目标元素的之后的所有元素进行移动了。</p>
<p>链表的单个节点一般为结构体或者对象，因为链表的单个节点除了需要保存数据之外还需要维护它的相邻节点的关系，如果想获得链表中的某个节点的值，需要从链表的头结点开始遍历，直到找到需要的东西，而插入或者删除某个节点的话，需要找到相应的节点，修改其以及其相邻节点的相关指针的引用即可。</p>
<p>像其他的数据结构，比如 队列，栈，树，都可以通过数组或者链表来组织，并实现相应的操作功能。</p>
<span id=Hash_Table>
<h2>二.Hash Table</h2>
</span>
<p>这个世界上没有十全十美的东西，所以我们要学会取舍。任何技术的实现都没有最好的只要最合适的，也就说实现的最佳方案是和应用场景息息相关的。<br>很多时候，我们想对数据进行快速的存取（比如缓存的实现），并用一个key来标记自己存取的数据。我们可以把它叫做key-value的结构。<br>说到&#8220;快速&#8221;我们很快想到数组，因为数组可以在O(1)的时间复杂内完成指定位置元素的读写操作。<br>所以在理想状态，如果一个数组足够长，且存在一个函数可以将每一个key映射到唯一的一个数组下标，那么我们就可以很完美的解决问题。但往往资源都是有限的，我们没有那么大的空间，也不能设计一个无比负责的映射算法保证每一个key对应到一个唯一的数组下标。所以我们会选择一些折中的方案。</p>
<p>hash table便是为解决这类问题而存在的。</p>
<span id=1.>
<h3>1.哈希函数</h3>
</span>
<p>Hash或者你可以翻译成散列或者杂凑，hash操作其本质上就是将一个数据映射成另一个数据，通常情况下原数据的长度比hash后的数据容量大。<br>这种映射的关系我们叫做哈希函数。<br><img class="alignnone size-full wp-image-30878" title=300px-Hash_table_4_1_1_0_0_0_0_LL.svg alt="" src="http://geeklu.com/wp-content/uploads/2010/07/300px-Hash_table_4_1_1_0_0_0_0_LL.svg_.png" width=300 height=270><br>一般情况下 哈希函数的输入可能的总数要远远多于哈希值所能表示的总数，所以就有可能两个不同的输入对应同一个哈希值，通常把具有不同关键码而具有相同哈希值的记录称作&#8220;同义词&#8221;。<br>在信息安全领域中也经常使用到哈希函数，不过需要使用的是单向哈希函数，就是无法通过哈希的结果反推出输入，所以经常应用于密码的加密，传输内容的完整性检查，在安全领域常用的哈希算法有 MD5，SHA1等。<br>在哈希表的应用中，哈希函数常用余数法进行，也就是通过求模的方式算出哈希值。</p>
<span id=2.>
<h3>2.哈希表</h3>
</span>
<p>哈希表是一种数据结构，实现key-value的快速存取。之前说过数组可以实现快速存取，所以哈希表肯定会使用到数组。在这里，我们把每一个数组的单元叫做一个bucket（桶）。</p>
<span>
<h5>构造哈希函数</h5>
</span>
<p>这里哈希函数的作用就是将key映射到一个存储地址。所以构造一个哈希表我们得先构造哈希函数。<br>如果一个key哈希后对应地址中已经存放了值了，这种情况我们叫做哈希冲突（Hash collisions）。<br>如果存在一个哈希函数，使得每一个输入都能对应到唯一的一个存储单元中（没有冲突），那么这样的哈希函数我们可以叫它完美哈希函数（Perfect Hash Function，简称PHF)。<br>但为了哈希函数简单，运行速度快，往往不会使用完美哈希函数。所以冲突肯定会存在的，为了减少冲突，我们希望哈希函数的结果均匀的分布在地址单元的空间中。这样可以有效的减少冲突。<br><img class="alignnone size-full wp-image-30876" title=Hash_table alt="" src="http://geeklu.com/wp-content/uploads/2010/07/Hash_table.png" width=315 height=230><br>装填因子Load factor a=哈希表的实际元素数目(n)/ 哈希表的容量(m) a越大，哈希表冲突的概率越大，但是a越接近0，那么哈希表的空间就越浪费。<br>一般情况下建议Load factor的值为0-0.7，Java实现的HashMap默认的Load factor的值为0.75，当装载因子大于这个值的时候，HashMap会对数组进行扩张至原来两倍大。</p>
<span id=_1>
<h5>冲突解决</h5>
</span>
<p>既然冲突不可避免，那么我们就必须对冲突进行解决(总不能把之前的内容覆盖掉把),<br>解决冲突的方式主要分两类<br>开放定址法(Open addressing)这种方法就是在计算一个key的哈希的时候，发现目标地址已经有值了，即发生冲突了，这个时候通过相应的函数在此地址后面的地址去找，直到没有冲突为止。这个方法常用的有线性探测，二次探测，再哈希。<br>这种解决方法有个不好的地方就是，当发生冲突之后，会在之后的地址空间中找一个放进去，这样就有可能后来出现一个key哈希出来的结果也正好是它放进去的这个地址空间，这样就会出现非同义词的两个key发生冲突。<br><br><br>链接法(Separate chaining)链接法是通过数组和链表组合而成的。当发生冲突的时候只要将其加到对应的链表中即可。</p>
<p><img class="alignnone size-full wp-image-30877" title=450px-Hash_table_5_0_1_1_1_1_1_LL.svg alt="" src="http://geeklu.com/wp-content/uploads/2010/07/450px-Hash_table_5_0_1_1_1_1_1_LL.svg_.png" width=450 height=310></p>
<p>与开放定址法相比，链接法有如下几个优点：<br>①链接法处理冲突简单，且无堆积现象，即非同义词决不会发生冲突，因此平均查找长度较短；<br>②由于链接法中各链表上的结点空间是动态申请的，故它更适合于造表前无法确定表长的情况；<br>③开放定址法为减少冲突，要求装填因子&#945;较小，故当结点规模较大时会浪费很多空间。而链接法中可取&#945;&#8805;1，且结点较大时，拉链法中增加的指针域可忽略不计，因此节省空间；<br>④在用链接法构造的散列表中，删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表，删除结点不能简单地将被删结点的空间置为空，否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中，空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作，只能在被删结点上做删除标记，而不能真正删除结点。</p>
<p>当然链接法也有其缺点，拉链法的缺点是：指针需要额外的空间，故当结点规模较小时，开放定址法较为节省空间，而若将节省的指针空间用来扩大散列表的规模，可使装填因子变小，这又减少了开放定址法中的冲突，从而提高平均查找速度。</p>
<hr>
注:部分图片来自Wikipedia 
<img src ="http://www.cppblog.com/elva/aggbug/131186.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2010-10-25 12:57 <a href="http://www.cppblog.com/elva/archive/2010/10/25/131186.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>