??xml version="1.0" encoding="utf-8" standalone="yes"?>
一步一步写q二叉?wi)(AVL?wi)?j)
作者:(x)C加 更新旉Q?/span>2012-8-20
q二叉?wi)(Balanced Binary TreeQ是二叉查找?wi)的一个进化体Q也是第一个引入^衡概늚二叉?wi)?962q_(d)G.M. Adelson-Velsky ?E.M. Landis发明?jin)这|(wi)Q所以它又叫AVL?wi)。^衡二叉树(wi)要求对于每一个节Ҏ(gu)_(d)它的左右子树(wi)的高度之差不能超q?Q如果插入或者删除一个节点得高度之差大?Q就要进行节点之间的旋{Q将二叉?wi)重新维持在一个^衡状态。这个方案很好的解决?jin)二叉查找?wi)退化成链表的问题,把插入,查找Q删除的旉复杂度最好情况和最坏情况都l持在O(logN)。但是频J旋转会(x)使插入和删除牺牲掉O(logN)左右的时_(d)不过相对二叉查找?wi)来_(d)旉上稳定了(jin)很多?/p>
q二叉?wi)实现的大部分过E和二叉查找?wi)是一L(fng)Q学q二叉?wi)之前一定要?x)二叉查找?wi)Q,区别在于插入和删除之后要写一个旋转算法去l持qQ维持^衡需要借助一个节炚w度的属性。我参考了(jin)机械工业出版C《数据结构与法分析-C语言描述》写?jin)一个C++版的代码。这本书的AVLTree讲的很好Q不q没有很完整的去描述。我?x)一步一步的讲解如何写^衡二叉树(wi)Q重Ҏ(gu)q二叉?wi)的核?j)部分Q也是旋{法?/p>
W一步:(x)节点信息
相对于二叉查找树(wi)的节Ҏ(gu)_(d)我们需要用一个属性二叉树(wi)的高度,目的是维护插入和删除q程中的旋{法?/p>
代码如下Q?/p>
W二步:(x)q二叉?wi)类的声?/strong>
声明中的旋{函数在后边的步骤中详解?/p>
代码如下Q?/p>
W三步:(x)两个辅助Ҏ(gu)
旋{法需要借助于两个功能的辅助Q一个是求树(wi)的高度,一个是求两个高度的最大倹{这里规定,一늩?wi)的高度?1Q只有一个根节点的树(wi)的高度ؓ(f)0Q以后每多一层高度加1。ؓ(f)?jin)解x(chng)针NULLq种情况Q写?jin)一个求高度的函敎ͼq个函数q是很有必要的?/p>
代码如下Q?/p>
W四步:(x)旋{
对于一个^衡的节点Q由于Q意节Ҏ(gu)多有两个儿子Q因此高度不qӞ此节点的两颗子树(wi)的高度差2.Ҏ(gu)看出Q这U不q出现在下面四U情况:(x)
1?节点的左子树(wi)3节点高度比右子树(wi)7节点?Q左子树(wi)3节点的左子树(wi)1节点高度大于叛_?节点Q这U情冉|?span data-mce-style="color: #ff0000;" style="color: #ff0000; ">左左?/p>
2?节点的左子树(wi)2节点高度比右子树(wi)7节点?Q左子树(wi)2节点的左子树(wi)1节点高度于叛_?节点Q这U情冉|?span data-mce-style="color: #ff0000;" style="color: #ff0000; ">左右?/p>
3?节点的左子树(wi)1节点高度比右子树(wi)5节点?Q右子树(wi)5节点的左子树(wi)3节点高度大于叛_?节点Q这U情冉|?span data-mce-style="color: #ff0000;" style="color: #ff0000; ">叛_?/p>
4?节点的左子树(wi)1节点高度比右子树(wi)4节点?Q右子树(wi)4节点的左子树(wi)3节点高度于叛_?节点Q这U情冉|?span data-mce-style="color: #ff0000;" style="color: #ff0000; ">叛_?/p>
从图2中可以可以看出,1?两种情况是对U的Q这两种情况的旋转算法是一致的Q只需要经q一ơ旋转就可以辑ֈ目标Q我们称之ؓ(f)单旋转??两种情况也是对称的,q两U情늚旋{法也是一致的Q需要进行两ơ旋转,我们UC为双旋{?/p>
W五步:(x)单旋?/strong>
单旋转是针对于左左和叛_q两U情늚解决Ҏ(gu)Q这两种情况是对U的Q只要解决了(jin)左左q种情况Q右叛_很好办了(jin)。图3是左左情늚解决Ҏ(gu)Q节点k2不满_^衡特性,因ؓ(f)它的左子?wi)k1比右子树(wi)Z?层,而且k1子树(wi)中,更深的一层的是k1的左子树(wi)X子树(wi)Q所以属于左左情c(din)?/p>
Z?wi)恢复^衡,我们把k2变成q棵?wi)的根节点,因?f)k2大于k1Q把k2|于k1的右子树(wi)上,而原本在k1叛_?wi)的Y大于k1Q小于k2Q就把Y|于k2的左子树(wi)上,q样既满了(jin)二叉查找?wi)的性质Q又满?jin)^衡二叉树(wi)的性质?/p>
q样的操作只需要一部分指针改变Q结果我们得到另外一颗二叉查找树(wi)Q它是一A(ch)VL?wi),因?f)X向上一Ud?jin)一层,Yq停留在原来的层面上QZ向下Ud?jin)一层。整|(wi)的新高度和之前没有在左子?wi)上插入的高度相同,插入操作使得X高度镉K?sh)(jin)。因此,׃q颗子树(wi)高度没有变化Q所以通往根节点的路径׃需要l旋转了(jin)?/p>
代码如下Q?/p>
W六步:(x)双旋?/strong>
对于左右和右左这两种情况Q单旋{不能使它辑ֈ一个^衡状态,要经q两ơ旋转。双旋{是针对于q两U情늚解决Ҏ(gu)Q同L(fng)Q这样两U情况也是对U的Q只要解决了(jin)左右q种情况Q右左就很好办了(jin)。图4是左x(chng)늚解决Ҏ(gu)Q节点k3不满_^衡特性,因ؓ(f)它的左子?wi)k1比右子树(wi)Z?层,而且k1子树(wi)中,更深的一层的是k1的右子树(wi)k2子树(wi)Q所以属于左x(chng)c(din)?/p>
Z?wi)恢复^衡,我们需要进行两步,W一步,把k1作ؓ(f)根,q行一ơ右x(chng)转,旋{之后变成了(jin)左左情况Q所以第二步再进行一ơ左左旋转,最后得C(jin)一以k2为根的^衡二叉树(wi)?wi)?/p>
代码如下Q?/p>
W七步:(x)插入
插入的方法和二叉查找?wi)基本一P区别是,插入完成后需要从插入的节点开始维护一个到根节点的路径Q每l过一个节炚w要维持树(wi)的^衡。维持树(wi)的^衡要Ҏ(gu)高度差的特点选择不同的旋转算法?/p>
代码如下Q?/p>
W八步:(x)查找
和二叉查找树(wi)相比Q查找方法没有变法,不过Ҏ(gu)存储的特性,AVL?wi)能l持在一个O(logN)的稳定的旉Q而二叉查找树(wi)则相当不E_?/p>
代码如下Q?/p>
W九(ji)步:(x)删除
删除的方法也和二叉查找树(wi)的一_(d)区别是,删除完成后,需要从删除节点的父亲开始向上维护树(wi)的^衡一直到根节炏V?/p>
代码如下Q?/p>
W十步:(x)中序遍历
代码如下Q?/p>
W十一步:(x)关于效率
此数据结构插入、查扑֒删除的时间复杂度均ؓ(f)O(logN)Q但是插入和删除需要额外的旋{法需要的旉Q有时旋转过多也?x)?jing)响效率?/p>
关于递归和非递归。我用的是递归的方法进行插入,查找和删除,而非递归的方法一般来说要比递归的方法快很多Q但是我感觉非递归的方法写出来?x)比较困难,所以我q是选择?jin)递归的方法?/p>
q有一U效率的问题是关于高度信息的存储Q由于我们需要的仅仅是高度的差,不需要知道这|(wi)的高度,所以只需要用两个二q制位就可以表示q个差。这样可以避免^衡因子的重复计算Q可以稍微的加快一些速度Q不q代码也丧失?jin)相对简明性和清晰度。如果采用递归写法的话Q这U微加速就更显得微乎其微了(jin)?/p>
如果有哪些不对的或者不清晰的地方请指出Q我?x)修改ƈ加以完善?/p>
附:(x)完整代码
作者:(x)C加 更新旉Q?/span>2012-8-9
二叉查找?wi)?/span>BSTQ是二叉?wi)的一个重要的应用Q它在二叉树(wi)的基上加上了(jin)q样的一个性质Q对于树(wi)中的每一个节Ҏ(gu)_(d)如果有左儿子的话Q它的左儿子的g定小于它本n的|如果有右儿子的话Q它的右儿子的g定大于它本n的倹{?/span>
二叉查找?wi)的操作一般有插入、删除和查找Q这几个操作的^均时间复杂度都ؓ(f)O(logn)Q插入和查找操作很简单,删除操作?x)复杂一点,除此之外Q因Z叉树(wi)的中序遍历是一个有序序列,我就额外加上?jin)一个中序遍历操作?/span>
二叉查找?wi)的应用不是很多Q因为它最坏的时候跟U性表差不多,大部分会(x)应用到它的升U版Q^衡二叉树(wi)和红黑树(wi)Q这两棵?wi)都能把旉复杂度稳定?/span>O(logn)左右。虽然不?x)用刎ͼ但是二叉查找树(wi)是一定要学好的,毕竟它是q二叉?wi)和U黑?wi)的基础?/span>
接下来一步一步写一个二叉查找树(wi)模板?a href="/Files/cxiaojia/bst.zip">完整代码下蝲
W一步:(x)节点信息
二叉查找?wi)的节点和二叉?wi)的节点大部分是一L(fng)Q不同的是,二叉查找?wi)多了(jin)一个值出现的ơ数。如?/span>1昄?jin)二叉查找?wi)的节点信息?br />
代码如下Q?/span>
W二步:(x)二叉查找?wi)类的声?/span>
代码如下Q?/span>
W三步:(x)插入
Ҏ(gu)二叉查找?wi)的性质Q插入一个节点的时候,如果根节点ؓ(f)I,此节点作ؓ(f)根节点,如果根节点不为空Q就要先和根节点比较Q如果比根节点的值小Q就插入到根节点的左子树(wi)中,如果比根节点的值大插入到根节点的叛_?wi)中Q如此递归下去Q找到插入的位置。重复节点的插入用值域中的freq标记。如?/span>2是一个插入的q程?/span>
二叉查找?wi)的旉复杂度要看这|(wi)的Ş态,如果比较接近一一完全二叉树(wi)Q那么时间复杂度?/span>O(logn)左右Q如果遇到如?/span>3q样的二叉树(wi)的话Q那么时间复杂度׃(x)恢复到线性的O(n)?jin)?/span>
q二叉?wi)?x)很好的解军_?/span>3q种情况?/span>
插入函数的代码如下:(x)
W四步:(x)查找
查找的功能和插入差不多一P按照插入那样的方式递归下去Q如果找C(jin)Q就q回q个节点的地址Q如果没有找刎ͼp?/span>NULL?/span>
代码如下Q?/span>
W五步:(x)删除
删除?x)麻烦(ch)一点,如果是叶子节点的话,直接删除可以了(jin)。如果只有一个孩子的话,p它的父亲指向它的儿子Q然后删除这个节炏V图4昄?jin)一初始树(wi)?/span>4节点被删除后的结果。先用一个(f)时指针指?/span>4节点Q再?/span>4节点的地址指向它的孩子Q这个时?/span>2节点的右儿子变成了(jin)3节点Q最后删除(f)时节Ҏ(gu)向的I间Q也是4节点?/span>
删除有两个儿子的节点?x)比较复杂一些。一般的删除{略是用其右子树(wi)最的数据代替该节点的数据q归的删除掉叛_?wi)中最数据的节点。因为右子树(wi)中数据最的节点肯定没有左儿子,所以删除的时候容易一些。图5昄?jin)一初始树(wi)?/span>2节点被删除后的结果。首先在2节点的右子树(wi)中找到最的节点3Q然后把3的数据赋值给2节点Q这个时?/span>2节点的数据变?sh)?/span>3Q然后的工作是删除叛_?wi)中?/span>3节点?jin),采用递归删除?/span>
我们发现?/span>2节点叛_?wi)的查找q行?jin)两遍,W一遍找到最节点ƈ赋|W二遍删除这个最的节点Q这L(fng)效率q不是很高。你能不能写出只查找一ơ就可以实现赋值和删除两个功能的函数呢Q?/span>
如果删除的次C是很多的话,有一U删除的Ҏ(gu)?x)比较快一点,名字叫懒惰删除法Q当一个元素要被删除时Q它仍留在树(wi)中,只是多了(jin)一个删除的标记。这U方法的优点是删除那一步的旉开销可以避免了(jin)Q如果重新插入删除的节点的话Q插入时也避免了(jin)分配I间的时间开销。缺Ҏ(gu)?wi)的深度会(x)增加,查找的时间复杂度会(x)增加,插入的时间可能?x)增加?/span>
删除函数代码如下Q?/span>
W六步:(x)中序遍历
遍历的方法和二叉?wi)的?gu)一P写这个方法的目的呢,是输?gu)个二叉查找?wi)的有序序列?/span>
代码如下Q?/span>
到此Q整个代码就完成?jin),代码中肯定有很多不完善的地方h出,我会(x)加以完善Q谢谢?/span>
对于二叉查找?wi)不E_的时间复杂度的解x(chng)案有不少Q^衡二叉树(wi)、展树(wi)和红黑树(wi)都可以解册个问题,但效果是不一L(fng)?/span>
作者:(x)C加 更新旉Q?/span>2012-8-6
二叉?wi)首先是一|(wi)Q每个节炚w不能有多于两个的儿子Q也是?wi)的度不能超q?。二叉树(wi)的两个儿子分别称?#8220;左儿?#8221;?#8220;叛_?#8221;Q次序不能颠倒。如?是一个简单的二叉?wi)?br />
二叉?wi)的U类
一U是满二叉树(wi)Q除?jin)最后一层的叶子节点外,每一层的节点都必L两个儿子节点。如?是一个满二叉?wi)?br />
另一U是完全二叉?wi),一二叉树(wi)L最后一层后剩下的节点组成的?wi)?f)满二叉树(wi)Q最后一层的节点从左到右q箋(hu)Q没有空出的节点Q这L(fng)?wi)称为完全二叉?wi)。如?是一完全二叉树(wi)?br />
二叉?wi)的实?/span>
因ؓ(f)一|(wi)有最多只有两个儿子,所以我们可以用指针直接指向他们。一个节点包括|dataQ、指向左儿子的指针(lsonQ和指向叛_子的指针QrsonQ?/span>
二叉?wi)的插?删除,查找和链表差不多,不同的是需要指定是左儿子还是右儿子?/span>
二叉?wi)的数组实现也很单,假如说根节点在arr[0]q个位置Q那么它的左儿子在arr[2*0+1]Q也是arr[1]q个位置Q它的右儿子在arr[2*0+2] Q也是arr[2]q个位置。对于下标ؓ(f)i的节Ҏ(gu)_(d)它的左儿子的下标?*i+1Q右儿子的下标ؓ(f)2*i+2?/span>
二叉?wi)的遍?/span>
二叉?wi)的遍历有三U,分别为先序遍历,中序遍历和后序遍历。这三种遍历方式是根据根节点的读取顺序来分的Q?/span>
先序遍历Q就是最先读取根节点Q然后再d左子?wi)(按照同样的方法读取子树(wi)上的节点?j)Q最后读取右子树(wi)Q?/span>
中序遍历Q就是第二个d根节点,最先要d的是左子?wi),然后根节点,最后右子树(wi)Q?/span>
后序遍历Q就是最后一个读取根节点Q最先读取的是左子树(wi)Q第二个d叛_?wi),最后读取根节点?/span>
先序遍历的递归实现代码Q?/span>
作者:(x)C加 更新旉Q?/span>2012-8-3
无论是链表,栈还是队列,它们都是U性结构的Q每个节点的左边最多一个节点,双也最多一个节点,对于大量的输入数据,U性表的访问时间太慢,不宜使用。这里我要说一U非U性的数据l构Q其大部分操作的q行旉q_为O(logn)?/span>
我们涉及(qing)到的q种数据l构叫做?wi)。在计算机科学中Q树(wi)是非常有用的抽象概念。我们Ş象的LqC|(wi)Q一个家族的老祖可能有两个儿子,q两个儿子一个有一个儿子,一个有三个儿子Q像q样发展下去的一个族谱,是一个树(wi)Q如?所C?/span>
像一늜正的?wi)一P我们把老祖UCؓ(f)?wi)?两个字儿是分叉开的两个树(wi)枝,q两|(wi)枝可以l向下分成N个树(wi)枝,循环下去Q一直到长出叶子为止?/span>
我们把老祖或者树(wi)根称为根QrootQ节点,老祖的儿子称为子节点Q每个儿子作为根节点又可以Ş成一|(wi)Q我们把q样的树(wi)UCؓ(f)根节点的子树(wi)?/span>
?wi)的标准定义Q?/span>
?wi)(treeQ是包含nQn>0Q个节点的有I集合,其中Q?/span>
Q?Q每个元素称点(nodeQ;
Q?Q有一个特定的节点被称为根节点或树(wi)根(rootQ?/span>
Q?Q除根节点之外的其余数据元素被分为mQm≥0Q个互不怺的结合T1QT2Q?#8230;…Tm-1Q其中每一个集合TiQ?<=i<=mQ本w也是一|(wi)Q被UC原树(wi)的子?wi)(subtreeQ?/span>
?wi)具有以下特点?x)
Q?Q?span style="font-size: 7pt; line-height: normal; font-family: 'Times New Roman'; "> 每个节点有零个或多个子节炏V?/span>
Q?Q?span style="font-size: 7pt; line-height: normal; font-family: 'Times New Roman'; "> 每个子节点只有一个父节点?/span>
Q?Q?span style="font-size: 7pt; line-height: normal; font-family: 'Times New Roman'; "> 没有父节点的节点UCؓ(f)根节炏V?/span>
关于?wi)的一些术?/span>
节点的度Q一个节点含有的子树(wi)的个数称节点的度Q?/span>
叶节Ҏ(gu)l端节点Q度为零的节点称为叶节点Q?/span>
非终端节Ҏ(gu)分支节点Q度不ؓ(f)零的节点Q?/span>
双亲节点或父节点Q若一个结点含有子节点Q则q个节点UCؓ(f)其子节点的父节点Q?/span>
孩子节点或子节点Q一个节点含有的子树(wi)的根节点UCؓ(f)该节点的子节点;
兄弟节点Q具有相同父节点的节点互UCؓ(f)兄弟节点Q?/span>
?wi)的高度或深度?x)定义一|(wi)的根l点层次?Q其他节点的层次是其父结点层ơ加1。一|(wi)中所有结点的层次的最大值称|(wi)的深度。节点的层次Q从根开始定义vQ根为第1层,根的子结点ؓ(f)W?层,以此cLQ?/span>
?wi)的度?x)一|(wi)中,最大的节点的度UCؓ(f)?wi)的度?/span>
节点的祖先:(x)从根到该节点所l分支上的所有节点;
子孙Q以某节点ؓ(f)根的子树(wi)中Q一节点都称节点的子孙?/span>
林Q由mQm>=0Q棵互不怺的树(wi)的集合称为森林;
?wi)的实?/span>
节点的代码如?
?wi)的应?/strong>
大部分操作系l的目录l构是采用?wi)结构?/span>
?wi)的U类有很多,?wi)所扩展出来的很多数据结构都有着很大的作用,比如说红黑树(wi)QB?wi),后缀?wi)等{,q将在日后写到?/span>
作者:(x)C加 更新旉Q?/span>2012-8-2
像栈一P队列QqueueQ也是一U线性表Q它的特性是先进先出Q插入在一端,删除在另一端。就像排队一P刚来的h入队QpushQ要排在队尾(rear)Q每ơ出?pop)的都是队?front)的h。如?Q描qC(jin)一个队列模型?/span>
和栈一P队列也有数组实现和链表实CU,两种实现都能l出快速的O(1)q行旉Q区别在于链表实现指针域要占用空_(d)频繁的new和delete?x)消耗不的旉开销Q数l实现唯一的缺Ҏ(gu)建立时要定I间大小?/span>
假如一个队列最多只能站10个hQ当占满10个h后,W?1个h׃能入队,q种情况成ؓ(f)溢出。而如果第一个h出队?jin),剩下?个h依然q在原来的位|,队列里空Z(jin)一个位|,但第11个hq是不能入队Q这U情冉|为假溢出。克服假溢出有效的办法是使用循环队列?/span>
循环队列是把队֒队首q接hQŞ成一个环Q队下一个位|就是队首,q样可以有效的防止假溢出现象Q但队列的实际容量已然固定?/span>
队列的实?/span>
队列的数l实现和栈差不多Q不同的是,栈用top做下标,队列用front和rear作ؓ(f)下标?/span>
我更改了(jin)单链表的模板来实C个简单的队列。代码仅供学?fn),不之处q请指明Q我?x)对不之处q行修改和更新?/span>
代码如下Q?br />
队列的应?br />
打印机处理作业采用的是队列l构Q它们会(x)按照提交的顺序排列v来。STL也给Z(jin)一个强大的队列Q我们直接可以去用它?/span>
队列相关问题
如何用两个栈模拟一个队列,如果用两个队列模拟一个栈Q?/span>
作者:(x)C加 更新旉Q?/span>2012-8-1
栈(stackQ是限制插入和删除只能在一个位|上q行的线性表Q该位置在表的末端,叫做栈顶。添加元素只能在节点后dQ删除元素只能删除尾节点Q查看节点也只能查看节炏V添加、删除、查看依ơؓ(f)入栈QpushQ、出栈(popQ、栈节点(topQ。Ş象的_(d)栈是一个先q后出(LIFOQ表Q先q去的节点要{到后边q去的节点出来才能出来?/span>
如图1Q是一个栈的Ş象图Qtop指针指向的是栈顶节点Q所以我们可以通过top讉K?节点Q但??节点׃先于2q入q个表,所以是不可见的。如果把0节点当做头节点,2节点当做节点,那么栈限制了(jin)讉K权限Q只可以讉K节炏V?/span>
如图2Q当d一个节?的时候,只能在栈节点,也就是尾节点后添加,q样3节点变成?jin)栈Ӟ?节点变成?jin)不可见节点Q访问的时候只能访问到3节点。入栈时限制?jin)插入地址Q只能在栈顶d节点?/span>
当我们执行出栈的命o(h)Ӟ?的栈元素是3节点Q删除的时候只能允许删除栈的元素Q这样子3节点被删除,top指向删除后的栈顶2节点Q如?所C?/span>
栈有两种是实现结构,一U是序存储l构Q也是利用数组实现Q一U是铑ּ存储l构Q可以用单链表实现。数l实现栈很简单,用一个下标标记top来表C栈Ӟtop==-1Ӟ栈空Qtop==0Ӟ表示栈里只有一个元素,通过讉KtopZ标的数组元素卛_。出栈top自减Q入栈top自加O(jin)K?jin)?/span>
单链表实现栈要比单链表的实现单点。我们通过在表的尾端插入来实现pushQ通过删除节Ҏ(gu)实现popQ获取尾节点的元素来表示top。我修改?jin)链表那一章的单链表代码,把头节点当做栈顶节点Q实C(jin)一个简单的栈模板,仅供学习(fn)所用。代码会(x)不定时更新?a href="/Files/cxiaojia/stack.rar">代码下蝲
代码如下Q?br />
很清楚,除了(jin)clear函数外,所有的Ҏ(gu)的时间复杂度都是O(1)。这U实现方式的~点在于?span lang="EN-US">new?span lang="EN-US">delete的调用的开销是昂늚Q所以采用数l的方式实现?x)更好一炏V?br />
栈的应用
使用栈的时候一般不用自己重新去写,因ؓ(f)STLl我们实C(jin)一个很安全的栈Q可以放?j)去使用?/span>也可以用数组模拟一个,很简单?/span>
~译器调用函数就用了(jin)栈结构,当第一个函数还没执行完毕,调用W二个函数的时候,~译器就?x)把W一个函数压栈,W二个函数调用完毕的时候,׃(x)取栈函敎ͼ也就是第一个函数l执行?span lang="EN-US">
qW号 http://acm.nyist.net/JudgeOnline/problem.php?pid=2
中缀转后~ http://acm.nyist.net/JudgeOnline/problem.php?pid=467
后缀试求?http://acm.nyist.net/JudgeOnline/problem.php?pid=35
poj 3250 http://acm.pku.edu.cn/JudgeOnline/problem?id=3250
poj 1363 http://acm.pku.edu.cn/JudgeOnline/problem?id=1363
poj 1208 http://acm.pku.edu.cn/JudgeOnline/problem?id=1208
poj 1686 http://acm.pku.edu.cn/JudgeOnline/problem?id=1686
poj 3250 http://acm.pku.edu.cn/JudgeOnline/problem?id=2045
hdu 1022 http://acm.hdu.edu.cn/showproblem.php?pid=1022
作者:(x)C加 更新旉Q?/span>2012-7-31
谈到链表之前Q先说一下线性表。线性表是最基本、最单、也是最常用的一U?/span>数据l构。线性表中数据元素之间的关系是一对一的关p,即除?jin)第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表有两U存储方式,一U是序存储l构Q另一U是铑ּ存储l构?/span>
序存储l构是两个盔R的元素在内存?sh)也是相(c)。这U存储方式的优点是查询的旉复杂度ؓ(f)O(1)Q通过首地址和偏U量可以直接访问到某元素,关于查找的适配法很多Q最快可以达到O(logn)。缺Ҏ(gu)插入和删除的旉复杂度最坏能辑ֈO(n)Q如果你在第一个位|插入一个元素,你需要把数组的每一个元素向后移动一位,如果你在W一个位|删除一个元素,你需要把数组的每一个元素向前移动一位。还有一个缺点,是当你不确定元素的数量Ӟ你开的数l必M证能够放下元素最大数量,遗憾的是如果实际数量比最大数量少很多Ӟ你开的数l没有用到的内存只能浪Ҏ(gu)?jin)?/span>
我们常用的数l就是一U典型的序存储l构Q如??/span>
铑ּ存储l构是两个盔R的元素在内存?sh)可能不是相(c)Q每一个元素都有一个指针域Q指针域一般是存储着C一个元素的指针。这U存储方式的优点是插入和删除的时间复杂度为O(1)Q不?x)浪费太多内存,d元素的时候才?x)申请内存,删除元素会(x)释攑ֆ存,。缺Ҏ(gu)讉K的时间复杂度最坏ؓ(f)O(n)Q关于查扄法很少Q一般只能遍历,q样旉复杂度也是线性(O(n)Q的?频繁的申请和释放内存?sh)?x)消耗时间?/span>
序表的Ҏ(gu)是随机dQ也是讉K一个元素的旉复杂度是O(1)Q链式表的特性是插入和删除的旉复杂度ؓ(f)O(1)。要Ҏ(gu)实际情况去选取适合自己的存储结构?/span>
链表是铑ּ存储的线性表。根据指针域的不同,链表分ؓ(f)单向链表、双向链表、@环链表等{?/span>
一?单向链表QslistQ?/span>
链表中最单的一U是单向链表Q每个元素包含两个域Q值域和指针域Q我们把q样的元素称之ؓ(f)节点。每个节点的指针域内有一个指针,指向下一个节点,而最后一个节点则指向一个空倹{如?是一个单向链表?/span>
一个单向链表的节点被分成两个部分。第一个部分保存或者显C关于节点的信息Q第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历?/span>
我写?jin)一个简单的C++版单向链表类模板Q就用这D代码讲解一下一个具体的单向链表该怎么写(代码仅供学习(fn)Q,当然首先你要具备C++基础知识和简单的模板元编E?br />完整代码
首先我们要写一个节点类Q链表中的每一个节点就是一个节点类的对象。如??br />
代码如下Q?/span>
W二步,写单链表cȝ声明Q包括属性和Ҏ(gu)?/span>
代码如下Q?/span>
W三步,写构造函敎ͼ初始化链表类的属性?/span>
代码如下Q?/span>
W四步,实现add()Ҏ(gu)?/span>
代码如下Q?/span>
W五步,实现traversal()函数Q遍历ƈ输出节点信息?/span>
代码如下Q?br />
W六步,实现isEmpty()函数Q判断链表是否ؓ(f)I,q回真ؓ(f)I,假则不空?/span>
代码如下Q?/span>
W七步,实现find()函数?/span>
代码如下Q?br />
W八步,实现delete()函数Q删除第一个gؓ(f)x的节点,如图4?/span>
代码如下Q?br />
W九(ji)步,实现insert()?span lang="EN-US">insertHead()函数Q在p节点后插入gؓ(f)x的节炏V如?span lang="EN-US">5?span lang="EN-US">
代码如下Q?/span>
最l,我们完成一个简单的单向链表。此单向链表代码q有很多待完善的地方Q以后会(x)修改代码q不定时更新?/span>
二?双向链表
双向链表的指针域有两个指针,每个数据l点分别指向直接后和直接前驱。单向链表只能从表头开始向后遍历,而双向链表不但可以从前向后遍历,也可以从后向前遍历。除?jin)双向遍历的优点Q双向链表的删除的时间复杂度?x)降为OQ?Q,因ؓ(f)直接通过目的指针可以找到前p点,单向链表得从表头开始遍历寻扑։p炏V缺Ҏ(gu)每个节点多了(jin)一个指针的I间开销。如?是一个双向链表?/span>
三?循环链表
循环链表是让链表的最后一个节Ҏ(gu)向第一个节点,q样Ş成了(jin)一个圆环,可以循环遍历。单向@环链表可以单向@环遍历,双向循环链表的头节点的指针也要指向最后一个节点,q样的可以双向@环遍历。如?是一个双向@环链表?/span>
四?链表相关问题
1、如何判断一个单链表有环
2、如何判断一个环的入口点在哪?/span>
3、如何知道环的长?/span>
4、如何知道两个单链表Q无环)(j)是否怺
5、如果两个单链表Q无环)(j)怺Q如何知道它们相交的W一个节Ҏ(gu)什?/span>
6、如何知道两个单链表Q有环)(j)是否怺
7、如果两个单链表Q有环)(j)怺Q如何知道它们相交的W一个节Ҏ(gu)什?/span>
hash_mapQ顾名思义Q就是利?/span>hash_set存储l构的写map映照容器Q普通的map用的是红黑树(wi)存储l构写的。元素的(g)索时间对比,hash_setq似?/span>O(1)Q红黑树(wi)?/span>O(logn)?/span>Hash_map的空间开销要比map 的空间开销大,其是我用数l模拟指针写?/span>hash_map?/span>
所以,如果有必要采取映结构的时候,能用hash_map别?/span>map?/span>
需要注意的是,hash_map遍历出来的元素不是有序的?/span>
Hash_map和普?/span>hash_set相比的话Q?/span>hash_map?/span>hash_set多了(jin)一?/span>value表,也就是映表?/span>
下面?/span>hash_map的模板,?/span>hash_set的模板差不多?br />
l你一串数字,让你求出某个子串中某个数字出现的ơ数?/span>
用邻接表储存每个数出现的位置Q然后对L表进行二分查找,扑և区间?/span>
作者:(x)C加 更新旉Q?/span>2012-8-16
Ƣ迎自荐?span style="color: red; ">推荐链接。请于留a处告知?br />