1、链表前面大家用队、栈的时候用的都是数组。数组挺好用的,不过蛋疼的是,数组里面要增、删数据就蛋疼了。不过咱们有链表,下面我来丑陋地给大家画个链表哈哈,说了画给大家,就果断要亲手画,果然很丑陋的说 O_o我来简单给大家先说一下链表的每个节点都是怎么个结构看中间那一坨,这就是一个节点,prev 部分指向它的前驱,而 next 则指向它的后继。这样,该节点就成功得与前后向链接了。而中间那个 data(不是 dota 哟)就是这个节点的数据部分,当然不一定只有一个,画成一个比较清晰而已。大家看到最右边节点的后继中是个“/” ,这代表着指向null,即空。碰到这种情况,就是说,这已经是链表的一端了。当然头
2、和尾都有可能。最左边那个 head()就是这个链表的头指针,头指针可以让你能够找到链表中的第一个元素,大家应该明白,链表不像数组,可以通过下标直接找到节点(优化除外) ,这时候,你要用链表中某一个节点的时候就得从头指针往后遍历。我们可以看到,链表比数组好在它能够很方便的在某处增加节点或删除节点,但同时,数组对于每个元素的定位又更加快速两个数据结构都各有千秋,大家要结合实际问题灵活选用。下面来将一些具体的函数:LIST-SEARCH(L,k)X headLWhile x NIL and keyx kDo x nextxReturn xPS: 这是在链表中搜索某结点的一个方法。L 是一个链表,k
3、是我们要搜索的 KEY,我们先把 L 的头指针给 x,然后当 x 还指向链表中节点并且没有发现 KEY 值的时候做 x nextx这件事,那这件事是什么意思呢?有的孩纸应该看明白了,就是将 x 指向当前节点的后继,从而达到链表遍历的目的。LIST-INSERT(L,x)nextx headLIf headL NILThen prevheadL xHeadL xPrevx NILPS:这是在链表头插入节点的方法。L 是一个链表,x 是一个节点(这个时候 x 还是孤零零的一个) 。我们先让 x 的后继指向本来的头结点。如果头结点不为空,就将它的前驱指向 x(之前前驱为空) 。此时,头指针指向 x。
4、最后将 x 的前驱赋为空。大家可以自己在草稿纸上画一下上述过程。如果是在链表中插入结点,类似,希望童鞋们自行考虑该如何操作,只要注意四个地方的指向(可能会有点绕,不过结构是很对称的):前驱,后继,前驱的后继,后继的前驱。搞清楚这四个的关系,其实也是链表最核心的东西LIST-DELETE(L,x)If prevx NILThen nextprevx nextxElse headL nextxIf nextx NILThen prevnextx prevxPS: 这是在链表中删除一个节点。L 是一个链表,x 是一个节点。首先如果 x 的前驱不为空,那么就将前驱的后继指向 x 本来的后继,否则 x
5、是头结点,那么直接将头指针指向 x 的后继。接下来,如果后继不为空则将后继的前驱指向 x 本来的前驱,当然如果是空的话,你不用理会。删除操作的核心,还是我们刚刚提到的四点:前驱,后继,前驱的后继,后继的前驱。大家看一眼下图,这是在链表中删除节点的情况。所对应的就是第一个if 的操作,它将前驱(prevx)的后继(nextprevx = nextx )指向了原本的后继(nextx) 。第二步类似。对于链表还有一种哨兵的优化,如果大家有兴趣可以自行百度谷歌,也可以联系我来讨论哈。树(二叉树)链表整完了,咱们来说说树,这里我们重点讲二叉树。树的每个节点结构与链表挺类似的,只是原来的前驱、后继变为了父
6、亲、左右孩子。在网上给大家粘了个图下来,L 就是左孩子,R 就是右孩子,这个图没有把父亲的域画出来,大家应该可以明白是什么意思。我们以后再画二叉树就不会再带着左右两个域,请大家注意。最顶端的节点叫做根,当然也有单独把根拿出来的情况,个人感觉看情况吧。而最底层的节点(没有孩子节点)的叫做叶子节点。有了链表的基础,我相信这里的增、删、查找大家应该可以自行完成,就算不会我估计看代码也可以看得懂了,我就不再这矫情地一点一点分析了。直接来给大家讲一讲二叉树的一种建树方法,以及依靠递归来实现的三序遍历。首先来提一下三序遍历:先序遍历:首先访问根结点然后遍历左子树,最后遍历右子树。什么意思呢?遍历一棵树我们
7、可以把问题分解开。遍历一整棵树,我们可以先访问根节点(这显然可以做到) ,然后遍历左子树,最后遍历右子树。可以看到我们把原来的问题分划成了三个子问题:第一个访问根节点,我们已经解决。后两个问题是,分别遍历左右子树。 (场外观众小贤:咦?这不是原来的问题?) bingo!我们把原来的问题分成了三个规模更小的问题,一个已经解决,另两个跟原来的问题一样。这就是传说中的分治算法,分而治之!对于遍历左子树的问题,我们同样可以先访问左子树的根,再遍历下面的左右子树分划啊分划直到分化到某一个叶节点的时候,访问该节点,无左右孩子,退回上一层。建议童鞋们自己画一个三层左右的二叉树,自己按这个过程体会一下分治的思
8、想,分治的思想搞清楚之后,进一步去理解递归就会轻松的多了。下面我给大家粘一段我暑假作业的代码。是 java 的,我就不用伪代码了。T T 导论上没有,不敢瞎写。static public void preOrder(Node node)if (node != null)node.print();preOrder(node.leftChild);preOrder(node.rightChild);elseSystem.out.print(#);PS:我给大家解读一下这段代码,不是很难。preOrder 就是先序遍历。Node node 说的是传进来一个 Node 类型的 node,它是一个节点,
9、(Node 类有 leftChild 和 rightChild两个域,还有 data。)这就是我们要遍历子树的根节点。如果节点不为空的话我们先打印出node 本身,然后我们分别先序遍历左右孩子,如果为空看具体情况操作,我编的这段代码是用#来表示 NIL。这里递归的理解的确是个难点,大家静下心来手动走几遍过程就会改善许多。 (艾玛累死了,苏海过来给爷揉揉肩)相信有了先序的铺垫,中序和后序就会轻松很多。中序遍历:中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。后序遍历:后序遍历首先遍历左子树,然后遍历右子树,最后遍历访问根结点。在递归实现的情况下,这三个几乎没有差别,就是在上面 if 语句
10、中颠倒一下几个语句的位置。而在用栈来实现非递归的三序遍历时就会有较大的差别了至少本菜自己编的时候这三种顺序差别很大。讲如何去建立一棵树:建树的方法有很多,我只讲一下我自己最常用的一种方法。输入的数据是按照先序给出的二叉树, #代表叶节点。比如 ABC#DE#G#F#就会建立出如下的一棵二叉树:下面给出 java 的一段代码:static Node createTree()Node node = null;if ( chi != #)node = new Node();node.data = chi;i+;node.leftChild = createTree();node.rightChild = createTree();elsei+;return node;PS: i 是 Node 类的一个静态变量,记录建树的进度。首先创建一个空结点,如果表达式中当前值不是# ,那么给 node 赋值,再分别给左右孩子建树。这是按照先序来的。跟刚刚先序遍历几乎一样,大家可以对照看看,不过多敖述。下面给出的这个链接中,是一棵二叉排序树,它的建树规则就是左子树中的数= 根中的数=右子树中的数http:/www.cs.usfca.edu/galles/visualization/BST.html