韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理

时间:2023-02-12 09:39:22

文西马龙:http://blog.csdn.net/wenximalong/

链表——双向链表

韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理

关于按照英雄的排行顺序加入,参考我的这篇博文中的详细图文说明:韩顺平_PHP程序员玩转算法公开课(第一季)03_单链表crud操作之_水浒英雄排行算法_学习笔记_源代码图解_PPT文档整理

现在分析添加的情况
已经有1号英雄和5号英雄,现在要添加3号英雄
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理
此时cur指向了1号英雄,hero指向3号英雄
cur指向1号英雄,发现cur的下一个是5号英雄,大于要添加的3号英雄
分析图
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理
过程

(1)让3号英雄指向5号英雄,即把这个①号线搭起来
$hero->next=$cur->next; 
//$cur指向1号英雄,$cur->next指向5号英雄
(2)然后再搭pre这根线,让3号英雄的pre指向1号英雄,即把这个②号线搭起来
$hero->per=$cur;
(3)然后让5号英雄的pre指向3号英雄,即把这个③号线搭起来
$cur->next->pre=$hero;
//$cur->next指向5号英雄,$cur->next->pre为5号英雄指向1号英雄,即图中⑥号线
//此时⑥号线就断掉了,原来的5号英雄的pre是指向1号英雄的,现在指向了3号英雄
(4)让1号英雄指向3号英雄,即把这个④号线搭起来
$cur->next=$hero;
//此时⑤号线就断掉了,原来的$cur->next是指向5号英雄的,现在指向了3号英雄
★自己对照图,走一遍,一定要线清楚了
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理
doubleLink.php

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
</head>
<body>
<h1>双向链表完成英雄排行管理</h1>
<hr/>
<a href="#">查询英雄</a>
<a href="#">添加英雄</a>
<a href="#">删除英雄</a>
<a href="#">修改英雄</a>

<?php
//使用PHP面向对象的方式来完成

class Hero{
public $pre=null; //表示指向前一个节点的引用
public $no;
public $name;
public $nickname;
public $next=null; //表示指向后一个节点的引用

public function __construct($no='',$name='',$nickname=''){
$this->no=$no;
$this->name=$name;
$this->nickname=$nickname;
}

//添加hero,这里我们会构建一个双向链表
//这里使用了静态函数
public static function addHero($head,$hero){
//$head头节点不能动
//$cur为辅助节点,让$cur来穿针引线
$cur=$head;
//isExist 假设英雄不存在
$isExist=FALSE; //这是一个标志位

//首先看看目前的这个链表是不是空的
//当还有英雄的时候,$cur指向的$head节点的next属性必然为null,因为现在只有head节点,还没有添加英雄
//如果是空链表就直接加入
//怎样把空链表和普通链表并和在一起呢?
if($cur->next==null){ //如果是,就说明是空链表,添加第一个英雄
$cur->next=$hero;
//这样就首尾相连了
$hero->pre=$cur;
}else{ //当不是空节点的时候
//如果不是空节点,按照排名来添加

//首先找到添加的位置
while($cur->next!=null){ //只要$cur->next不等于null,就说明没有到队尾
if($cur->next->no > $hero->no){ //说明位置找到了,则说明$hero要加在$cur的后面
break;

}else if($cur->next->no==$hero->no){
$isExist=TRUE;
echo'<br/>不能添加相同的编号';
}
//继续下一个
$cur=$cur->next;
}

//退出该while循环时候,我们只需要把$hero添加到$cur后面即可,(队尾)
//说明还没有这个排名,可以添加,并可以和上面的空链表判断合并
if(!$isExist){ //如果不存在就添加,因为在上面break跳出的时候,就说明找到了,跳出的时候$isExist为假,则!$isExist为真
//添加,有点麻烦
$hero->next=$cur->next;
$hero->pre=$cur;
$cur->next->pre=$hero;
$cur->next=$hero;
}
}
}

//遍历显示双向链表,显示所有英雄的函数
public static function showHero($head){
//上来线做一个辅助指针,穿针引线
$cur=$head->next; //头本来就是空的,这样就输出打印的时候,不输出头了
//$cur=$head; 如果是这样,每次输出的时候会输出:编号= 名字= 外号= 把头也输出了

while($cur->next!=null){ //为空就说明到队尾了
echo'<br/>编号='.$cur->no.'名字='.$cur->name.'外号='.$cur->nickname;
//继续下移一步
$cur=$cur->next;
}

//当退出while的时候,$cur就指向了最后的那个节点了
//要是不输出,就会少一个人,少队尾的那个人
echo'<br/>编号='.$cur->no.'名字='.$cur->name.'外号='.$cur->nickname;
}
}

//创建一个头节点
$head=new Hero();
//创建一个英雄
$hero=new Hero(1,'宋江','及时雨');

//静态方法的调用
Hero::addHero($head,$hero);

$hero=new Hero(2,'卢俊义','玉麒麟');
Hero::addHero($head,$hero);

$hero=new Hero(6,'林冲','豹子头');
Hero::addHero($head,$hero);

$hero=new Hero(3,'吴用','智多星');
Hero::addHero($head,$hero);

$hero=new Hero(56,'孙二娘','母夜叉');
Hero::addHero($head,$hero);
echo '<br/>英雄排行榜.....';
//显示
Hero::showHero($head);
?>
</body>
</html>


=========
改进:不严密的地方,可以和前面的进行合并的
添加英雄,把添加时是空链表和不是空链表的情况,合并到一起了
现在考虑:要添加的人就在末尾

有一个head和1号人物,要添加的是6号人物
此时cur指向1号人物,
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理
当按照原先的代码执行,既下面这段代码,会有一些小问题的
$hero->next=$cur->next;
$hero->pre=$cur;
$cur->next->pre=$hero;
$cur->next=$hero;
执行
(1) $hero->next=$cur->next; 
此时cur指向1号人物,1号人物此时是最后的一个节点,$cur->next就是空了,那么$hero->next=$cur->next; 此时这句话就 没有意义了,因为$hero->next本身就是空啊,你再赋给它空,没有意义,干脆这句话此时就不执行了。所以要加一个一个判断条件,即$cur->next不等于空,执行$hero->next=$cur->next; 才有价值
if($cur->next!=null){
$hero->next=$cur->next;
}
(2)$hero->pre=$cur; 
这句话还是很有价值的,如下图所示, 把①号线搭起来了
(3)
$cur->next->pre=$hero;
此时cur指向1号人物,$cur->next就是空了,空的pre就 没意义了,所有也要和上面的(1)一样,加判断
if($cur->next!=null){
$cur->next->pre=$hero;
}
(4)$cur->next=$hero;
这句是有意义的,如下图所示,把 ②号线搭起 来了
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理

如果前面的代码修改成了如下的代码:
if($cur->next!=null){
$hero->next=$cur->next;
}
$hero->pre=$cur;
if($cur->next!=null){
$cur->next->pre=$hero;
}
$cur->next=$hero;

就和前面的一个if判断语句不谋而合了

即在doubleLink.php中的这段代码
if($cur->next==null){ //如果是,就说明是空链表,添加第一个英雄
$cur->next=$hero;
//这样就首尾相连了
$hero->pre=$cur;
}else{...

都是在说当$cur->next为空的时候,就执行
$cur->next=$hero;
$hero->pre=$cur;

所以可以把前面的if和现在的合并了,修改后的代码为
doubleLink2.php

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
</head>
<body>
<h1>双向链表完成英雄排行管理</h1>
<hr/>
<a href="#">查询英雄</a>
<a href="#">添加英雄</a>
<a href="#">删除英雄</a>
<a href="#">修改英雄</a>

<?php
//使用PHP面向对象的方式来完成

class Hero{
public $pre=null; //表示指向前一个节点的引用
public $no;
public $name;
public $nickname;
public $next=null; //表示指向后一个节点的引用

public function __construct($no='',$name='',$nickname=''){
$this->no=$no;
$this->name=$name;
$this->nickname=$nickname;
}

//添加hero,这里我们会构建一个双向链表
//这里使用了静态函数
//添加英雄,把添加时是空链表和不是空链表的情况,合并到一起了
public static function addHero($head,$hero){
//$head头节点不能动
//$cur为辅助节点,让$cur来穿针引线
$cur=$head;
//isExist 假设英雄不存在
$isExist=FALSE; //这是一个标志位

//首先看看目前的这个链表是不是空的
//当还有英雄的时候,$cur指向的$head节点的next属性必然为null,因为现在只有head节点,还没有添加英雄
//如果是空链表就直接加入
//怎样把空链表和普通链表并和在一起呢?

while($cur->next!=null){ //只要$cur->next不等于null,就说明没有到队尾
if($cur->next->no > $hero->no){ //说明位置找到了,则说明$hero要加在$cur的后面
break;

}else if($cur->next->no==$hero->no){
$isExist=TRUE;
echo'<br/>不能添加相同的编号';
}
//继续下一个
$cur=$cur->next;
}

//退出该while循环时候,我们只需要把$hero添加到$cur后面即可,(队尾)
//说明还没有这个排名,可以添加,并可以和上面的空链表判断合并
if(!$isExist){ //如果不存在就添加,因为在上面break跳出的时候,就说明找到了,跳出的时候$isExist为假,则!$isExist为真
//添加,有点麻烦
//当你添加的人物就在最后,比如现在只有1号英雄,再添加一个5号英雄,肯定是添加在1号英雄的后面
//这里加上if判断,会更稳健型些
if($cur->next!=null){
$hero->next=$cur->next;
}
$hero->pre=$cur;
if($cur->next!=null){
$cur->next->pre=$hero;
}
$cur->next=$hero;
}
}

//遍历显示双向链表,显示所有英雄的函数
public static function showHero($head){
//上来线做一个辅助指针,穿针引线
$cur=$head->next; //头本来就是空的,这样就输出打印的时候,不输出头了
//$cur=$head; 如果是这样,每次输出的时候会输出:编号= 名字= 外号= 把头也输出了

while($cur->next!=null){ //为空就说明到队尾了
echo'<br/>编号='.$cur->no.'名字='.$cur->name.'外号='.$cur->nickname;
//继续下移一步
$cur=$cur->next;
}

//当退出while的时候,$cur就指向了最后的那个节点了
//要是不输出,就会少一个人,少队尾的那个人
echo'<br/>编号='.$cur->no.'名字='.$cur->name.'外号='.$cur->nickname;
}
}

//创建一个头节点
$head=new Hero();
//创建一个英雄
$hero=new Hero(1,'宋江','及时雨');

//静态方法的调用
Hero::addHero($head,$hero);

$hero=new Hero(2,'卢俊义','玉麒麟');
Hero::addHero($head,$hero);

$hero=new Hero(6,'林冲','豹子头');
Hero::addHero($head,$hero);

$hero=new Hero(3,'吴用','智多星');
Hero::addHero($head,$hero);

$hero=new Hero(56,'孙二娘','母夜叉');
Hero::addHero($head,$hero);
echo '<br/>英雄排行榜.....';
//显示
Hero::showHero($head);
?>
</body>
</html>

代码不是一步到位的,优化前进


======
此时删除英雄就不需要辅助节点了
分析图
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理

当要删除的节点在最后的时候,还需要判断下
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理
此时$cur->next本身就是空了,所有要加判断了
$cur->pre->next=$cur->next; //这个就相当于把1号节点的next置空了,这个没错
所以要这样:
if($cur->next!=null){
$cur->next->pre=$cur->pre;
}
$cur->pre->next=$cur->next;
这样执行的话,把上图中的蓝线就去掉了,这样6号英雄就访问不到了,6号英雄出列了,因为我们是从表头开始找的,$cur-next就访问不到6号英雄节点了,6号英雄指向别人是没有用的,★关键★是没有人指向它。

注意:
要考虑删除第一个和最后一个,(当你的代码考虑不周全的时候)这是最容易出错的,所以要测试
韩顺平_PHP程序员玩转算法公开课(第一季)12_双向链表crud操作之_水浒英雄排行_学习笔记_源代码图解_PPT文档整理
doubleLink3.php

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
</head>
<body>
<h1>双向链表完成英雄排行管理</h1>
<hr/>
<a href="#">查询英雄</a>
<a href="#">添加英雄</a>
<a href="#">删除英雄</a>
<a href="#">修改英雄</a>

<?php
//使用PHP面向对象的方式来完成

class Hero{
public $pre=null; //表示指向前一个节点的引用
public $no;
public $name;
public $nickname;
public $next=null; //表示指向后一个节点的引用

public function __construct($no='',$name='',$nickname=''){
$this->no=$no;
$this->name=$name;
$this->nickname=$nickname;
}

//添加hero,这里我们会构建一个双向链表
//这里使用了静态函数
//添加英雄,把添加时是空链表和不是空链表的情况,合并到一起了
public static function addHero($head,$hero){
//$head头节点不能动
//$cur为辅助节点,让$cur来穿针引线
$cur=$head;
//isExist 假设英雄不存在
$isExist=FALSE; //这是一个标志位

//首先看看目前的这个链表是不是空的
//当还有英雄的时候,$cur指向的$head节点的next属性必然为null,因为现在只有head节点,还没有添加英雄
//如果是空链表就直接加入
//怎样把空链表和普通链表并和在一起呢?

while($cur->next!=null){ //只要$cur->next不等于null,就说明没有到队尾
if($cur->next->no > $hero->no){ //说明位置找到了,则说明$hero要加在$cur的后面
break;

}else if($cur->next->no==$hero->no){
$isExist=TRUE;
echo'<br/>不能添加相同的编号';
}
//继续下一个
$cur=$cur->next;
}

//退出该while循环时候,我们只需要把$hero添加到$cur后面即可,(队尾)
//说明还没有这个排名,可以添加,并可以和上面的空链表判断合并
if(!$isExist){ //如果不存在就添加,因为在上面break跳出的时候,就说明找到了,跳出的时候$isExist为假,则!$isExist为真
//添加,有点麻烦
//当你添加的人物就在最后,比如现在只有1号英雄,再添加一个5号英雄,肯定是添加在1号英雄的后面
//这里加上if判断,会更稳健型些
if($cur->next!=null){
$hero->next=$cur->next;
}
$hero->pre=$cur;
if($cur->next!=null){
$cur->next->pre=$hero;
}
$cur->next=$hero;
}
}

//删除某位英雄,现在不需要辅助节点了
public static function delHero($head,$herono){
//我们不使用辅助节点了
//先找到$head
//$head不能动
//直接让$cur指向下一个,$head->next就是具体的英雄了,当然还要先判断,$cur->next是不是空,要是空,就是光有head了,也没有要删除的了
$cur=$head->next;
$isFind=flase; //先搞个标志位
while($cur!=null){
if($cur->no==$herono){
//找到了,就置为true,并跳出
$isFind=true;
break;
}

//如果上面的if没有找到,就不停的向下走
$cur=$cur->next;
}

if($isFind){
//当$isFind为真的时候,就说明找到了
//执行删除
//自我删除
//这里考虑了要删除的英雄在末尾的时候情况
if($cur->next!=null){
$cur->next->pre=$cur->pre;
}
$cur->pre->next=$cur->next;
echo '<br/>要删除的英雄编号是'.$cur->no;
}else{
echo'<br/>要删除的英雄没有';
}
}

//遍历显示双向链表,显示所有英雄的函数
public static function showHero($head){
//上来线做一个辅助指针,穿针引线
$cur=$head->next; //头本来就是空的,这样就输出打印的时候,不输出头了
//$cur=$head; 如果是这样,每次输出的时候会输出:编号= 名字= 外号= 把头也输出了

while($cur->next!=null){ //为空就说明到队尾了
echo'<br/>编号='.$cur->no.'名字='.$cur->name.'外号='.$cur->nickname;
//继续下移一步
$cur=$cur->next;
}

//当退出while的时候,$cur就指向了最后的那个节点了
//要是不输出,就会少一个人,少队尾的那个人
echo'<br/>编号='.$cur->no.'名字='.$cur->name.'外号='.$cur->nickname;
}
}

//创建一个头节点
$head=new Hero();
//创建一个英雄
$hero=new Hero(1,'宋江','及时雨');

//静态方法的调用
Hero::addHero($head,$hero);

$hero=new Hero(2,'卢俊义','玉麒麟');
Hero::addHero($head,$hero);

$hero=new Hero(6,'林冲','豹子头');
Hero::addHero($head,$hero);

$hero=new Hero(3,'吴用','智多星');
Hero::addHero($head,$hero);

$hero=new Hero(56,'孙二娘','母夜叉');
Hero::addHero($head,$hero);
echo '<br/>英雄排行榜.....';
//显示
Hero::showHero($head);

echo'<br/>';
echo '<br/>删除后的英雄排行榜.....';
Hero::delHero($head,3);
Hero::showHero($head);

echo'<br/>';
echo'<br/>下面测试删除最前面的和最后面的英雄<br/>';
echo '<br/>删除后的英雄排行榜.....';
Hero::delHero($head,1);
Hero::showHero($head);

echo'<br/>';
echo '<br/>删除后的英雄排行榜.....';
Hero::delHero($head,56);
Hero::showHero($head);
?>
</body>
</html>

双向链表的好处:
双向链表可以完成自我删除,效率高一些,而且学明白双向链表后,为你将来,学习二叉树,霍夫曼树等等, 打下了良好的基础

通过学习,一定 要建立编程的思想,思考复杂的现象,在内存中是怎么倒腾的。


韩顺平_PHP程序员玩转算法公开课_学习笔记_源代码图解_PPT文档整理_目录