与数据结构,反转链表PHP实现

1.宽广情势分为迭代和递归,迭代是持久,递归是从尾到头
2.安装五个指针,old和new,每一项添加在new的末端,新链表头指针指向新的链表头
3.old->next无法一贯指向new,而是应该安装一个临时指针tmp,指向old->next指向的地方空间,保存原链表数据,然后old->next指向new,new往前移动到old处new=old,最后old=tmp取回数据
while(old!=null){
  tmp=old->next
  old->next=new
  new=old
  old=tmp
}

在linux内核中,有一种通用的双向循环链表,构成了各个队列的功底。链表的构造定义和有关函数均在include/linux/list.h中,上面就来宏观的牵线这一链表的各个API。

目录:
  1. 链表
  2. 二叉树
  3. 哈希表
  4. 简言之算法
  5. 架构

 

<?php
class Node{
        public $data;
        public $next;
}
//头插法创建一个链表
$linkList=new Node();
$linkList->next=null;//头结点
for($i=1;$i<=10;$i++){
        $node=new Node();
        $node->data="aaa{$i}";//创建新结点$node
        $node->next=$linkList->next;//$node->next指向头结点->next
        $linkList->next=$node;//头结点->next指向$node
}

var_dump($linkList);


function ReverseList($pHead){
        $old=$pHead->next;//跳过头结点
        $new=null;
        $tmp=null;
        //反转过程
        while($old!=null){
                $tmp=$old->next;
                $old->next=$new;
                $new=$old;
                $old=$tmp;
        }   
        //给新链表加个头结点
        $newHead=new Node();
        $newHead->next=$new;
        var_dump($newHead);
}
ReverseList($linkList);

object(Node)#1 (2) {
  ["data"]=>
  NULL
  ["next"]=>
  object(Node)#11 (2) {
    ["data"]=>
    string(5) "aaa10"
    ["next"]=>
    object(Node)#10 (2) {
      ["data"]=>
      string(4) "aaa9"
      ["next"]=>
      object(Node)#9 (2) {
        ["data"]=>
        string(4) "aaa8"
        ["next"]=>
        object(Node)#8 (2) {
          ["data"]=>
          string(4) "aaa7"
          ["next"]=>
          object(Node)#7 (2) {
            ["data"]=>
            string(4) "aaa6"
            ["next"]=>
            object(Node)#6 (2) {
              ["data"]=>
              string(4) "aaa5"
              ["next"]=>
              object(Node)#5 (2) {
                ["data"]=>
                string(4) "aaa4"
                ["next"]=>
                object(Node)#4 (2) {
                  ["data"]=>
                  string(4) "aaa3"
                  ["next"]=>
                  object(Node)#3 (2) {
                    ["data"]=>
                    string(4) "aaa2"
                    ["next"]=>
                    object(Node)#2 (2) {
                      ["data"]=>
                      string(4) "aaa1"
                      ["next"]=>
                      NULL
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
object(Node)#12 (2) {
  ["data"]=>
  NULL
  ["next"]=>
  object(Node)#2 (2) {
    ["data"]=>
    string(4) "aaa1"
    ["next"]=>
    object(Node)#3 (2) {
      ["data"]=>
      string(4) "aaa2"
      ["next"]=>
      object(Node)#4 (2) {
        ["data"]=>
        string(4) "aaa3"
        ["next"]=>
        object(Node)#5 (2) {
          ["data"]=>
          string(4) "aaa4"
          ["next"]=>
          object(Node)#6 (2) {
            ["data"]=>
            string(4) "aaa5"
            ["next"]=>
            object(Node)#7 (2) {
              ["data"]=>
              string(4) "aaa6"
              ["next"]=>
              object(Node)#8 (2) {
                ["data"]=>
                string(4) "aaa7"
                ["next"]=>
                object(Node)#9 (2) {
                  ["data"]=>
                  string(4) "aaa8"
                  ["next"]=>
                  object(Node)#10 (2) {
                    ["data"]=>
                    string(4) "aaa9"
                    ["next"]=>
                    object(Node)#11 (2) {
                      ["data"]=>
                      string(5) "aaa10"
                      ["next"]=>
                      NULL
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
  1. struct list_head {  
  2.     struct list_head *next, *prev;  
  3. };  
1. 链表

链表的社团
链表最中央的构造是在每个节点保存数据和到下一个节点的位置,在终极一个节点保存一个与众不同的截至标记。此外在一个稳住的地方保存指向第一个节点的指针,有的时候也会同时储存指向最后一个节点的指针。可是也可以提前把一个节点的岗位另外保存起来,然后径直访问。当然假诺只是访问数据就没必要了,不如在链表上囤积指向实际数据的指针。这样一般是为了访问链表中的下一个仍然前一个节点。
优势:可以打败数组链表需要事先掌握多少大小的先天不足,链表结构可以充足利用总计机内存空间,实现灵活的内存动态管理。链表最醒目标利益就是,常规数组排列关联项目标法门或者不同于这个多少项目在回想体或磁盘上挨家挨户,数据的存取往往要在不同的排列顺序中改换。而链表是一种自我指示数据类型,因为它含有指向另一个同等类另外数额的指针(链接),同时,链表允许插入和移除表上任意地点上的节点。
劣势:链表由于扩充了结点的指针域,空间开发比较大;其余,链表失去了数组随机读取的亮点,一般查找一个节点的时候需要从第一个节点开首每回访问下一个节点,向来访问到需要的职务。

单向链表
链表中最简单易行的一种是单向链表,
一个单向链表的节点被分成五个部分。它蕴含五个域,一个信息域和一个指针域。第一个部分保存如故展现关于节点的信息,第二个部分存储下一个节点的地点,而最后一个节点则针对一个空值。单向链表只可向一个大方向遍历。

双向链表
双向链表其实是单链表的改良,当我们对单链表举办操作时,有时你要对某个结点的间接前驱举办操作时,又无法不从表头开头查找。这是由单链表结点的布局所界定的。因为单链表每个结点只有一个储存直接后继结点地址的链域,那么能不可以定义一个既有囤积间接后继结点地址的链域,又有囤积直接前驱结点地址的链域的这样一个双链域结点结构吧?这就是双向链表。
在双向链表中,结点除含有数据域外,还有四个链域,一个储存直接后继结点地址,一般称之为右链域(当此“连接”为最终一个“连接”时,指向空值或者空列表);一个存储直接前驱结点地址,一般称之为左链域(当此“连接”为率先个“连接”时,指向空值或者空列表)。

循环链表
循环链表是与单向链表一样,是一种链式的蕴藏结构,所例外的是,循环链表的末尾一个结点的指针是指向该循环链表的首先个结点或者表头结点,从而结成一个环形的链。
循环链表的演算与单链表的演算基本一致。所不同的有以下几点:
1、在确立一个循环链表时,必须使其最终一个结点的指针指向表头结点,而不是象单链表这样置为NULL。此种境况还使用于在最终一个结点后插入一个新的结点。
2、在认清是否到表尾时,是判定该结点链域的值是否是表头结点,当链域值等于表头指针时,表明已到表尾。而非象单链表这样判断链域值是否为NULL。

块状链表
块状链表本身是一个链表,可是链表储存的并不是一般的数据,而是由这么些数量整合的顺序表。每一个块状链表的节点,也就是顺序表,可以被称为一个块。
块状链表另一个表征是争持于一般性链表来说节省内存,因为不用保存指向每一个数目节点的指针。

另外相关
链表的指出重点在于:顺序存储中的插入和删除的岁月复杂度是线性时间的,而链表的操作则可以是常数时间的复杂度。
链表的插入与删除操作顺序:
插入操作处理顺序:中间节点的逻辑,后节点逻辑,前节点逻辑。
删除操作的拍卖顺序:前节点逻辑,后节点逻辑,中间节点逻辑。

8.2 图的仓储结构

图的积存结构除了要存储图中逐条顶点的本身的音信外,同时还要存储顶点与极端之间的有着涉及(边的音信),因而,图的协会相比较复杂,很麻烦数据元素在存储区中的物理地点来代表元素之间的涉及,但也多亏由于其擅自的特性,故物理表示方法很多。常用的图的仓储结构有邻接矩阵、邻接表、十字链表和毗邻多重表。

 

这是链表的元素结构。因为是循环链表,表头和表中节点都是这一协会。有prev和next六个指针,分别指向链表中前一节点和后一节点。

2. 二叉树

树是一种相比首要的数据结构,尤其是二叉树。二叉树是一种特另外树,在二叉树中每个节点最多有六个子节点,一般称为左子节点和右子节点(或左孩子和右孩子),并且二叉树的子树有左右之分,其次序不可以随便颠倒。二叉树是递归定义的,由此,与二叉树有关的题材基本都可以用递归思想解决,当然有些问题非递归解法也应该控制,如非递归遍历节点等等

8.2.1  邻接矩阵表示法

对于一个持有n个顶点的图,可以使用n*n的矩阵(二维数组)来表示它们间的分界关系。图8.10和图8.11中,矩阵A(i,j)=1表示图中存在一条边(Vi,Vj),而A(i,j)=0代表图中不设有边(Vi,Vj)。实际编程时,当图为不带权图时,可以在二维数组中存放bool值,A(i,j)=true表示存在边(Vi,Vj),A(i,j)=false代表不设有边(Vi,Vj);当图带权值时,则可以直接在二维数组中存放权值,A(i,j)=null代表不设有边(Vi,Vj)。

 

澳门葡京备用网址 1
 

图8.10所示的是无向图的邻接矩阵表示法,可以洞察到,矩阵延对角线对称,即A(i,j)= A(j,i)。无向图邻接矩阵的第i行或第i列非零元素的个数其实就是第i个终端的度。这意味着无向图邻接矩阵存在必然的多寡冗余。

图8.11所示的是有向图邻接矩阵表示法,矩阵并不延对角线对称,A(i,j)=1表示顶点Vi邻接到顶点Vj;A(j,i)=1则代表顶点Vi分界自顶点Vj。两者并不象无向图邻接矩阵那样表示无异的趣味。有向图邻接矩阵的第i行非零元素的个数其实就是第i个终端的出度,而第i列非零元素的个数是第i个顶峰的入度,即第i个极点的度是第i行和第i列非零元素个数之和。

是因为存在n个顶点的图需要n2个数组元素举行仓储,当图为稀疏图时,使用邻接矩阵存储方法将出现大量零元素,照成极大地空间浪费,这时应该运用邻接表表示法存储图中的数据。

  1. #define LIST_HEAD_INIT(name) { &(name), &(name) }
      
  2.   
  3. #define LIST_HEAD(name) \   
  4.     struct list_head name = LIST_HEAD_INIT(name)  
  5.   
  6. static inline void INIT_LIST_HEAD(struct list_head *list)  
  7. {  
  8.     list->next = list;  
  9.     list->prev = list;  
  10. }  
3. 哈希表

散列表(Hash table,也叫哈希表),是依照重大码值(Key
value)而直白举办访问的数据结构。也就是说,它经过把重大码值映射到表中一个岗位来走访记录,以加速查找的进度。那几个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对自由给定的基本点字值key,代入函数后若能博取包含该重大字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash)
函数

8.2.2 邻接表表示法

图的邻接矩阵存储方法跟树的儿女链表示法相接近,是一种顺序分配和链式分配相结合的囤积结构。邻接表由表头结点和表结点两片段组成,其中图中各样终端均对应一个囤积在数组中的表头结点。如这一个表头结点所对应的终端存在相邻顶点,则把相邻顶点依次存放于表头结点所针对的单向链表中。如图8.12所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表举办仓储也会晤世数量冗余,表头结点A所指链表中留存一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。

澳门葡京备用网址 2
 

有向图的邻接表有出边表和入边表(又称逆邻接表)之分。出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点;入边表的表结点存放的则是指向表头结点的某个头顶点。如图8.13所示,图(b)和(c)分别为有向图(a)的出边表和入边表。

澳门葡京备用网址 3
 

如上所讨论的邻接表所表示的都是不带权的图,假若要代表带权图,可以在表结点中增添一个存放权的字段,其效率如图8.14所示。

澳门葡京备用网址 4
 

【注意】:观望图8.14得以窥见,当删除存储表头结点的数组中的某一要素,有可能使一些表头结点索引号的变动,从而导致周边修改表结点的气象时有暴发。可以在表结点中直接存放指向表头结点的指针以解决那个题材(在链表中存放类实例即是存放指针,但不可以不要保证表头结点是类而不是结构体)。在其实创设邻接表时,甚至可以采取链表代替数组存放表头结点或应用各种表存代替链表存放表结点。对所学的数据结构知识应当依照实际意况及所接纳语言的性状灵活使用,切不可生搬硬套。

【例8-1 
AdjacencyList.cs】图的邻接表存储结构

using System;
using System.Collections.Generic;
public class AdjacencyList<T>
{
    List<Vertex<T>> items; //图的终点集合
    public AdjacencyList() : this(10) { } //构造方法
    public AdjacencyList(int capacity) //指定容量的构造方法
    {
        items = new List<Vertex<T>>(capacity);
    }
    public void AddVertex(T item) //添加一个极限
    {   //不允许插入重复值
        if (Contains(item))
        {
            throw new ArgumentException(“插入了再一次顶点!”);
        }
        items.Add(new Vertex<T>(item));
    }
    public void AddEdge(T from, T to) //添加无向边
    {
        Vertex<T> fromVer = Find(from); //找到起先顶点
        if (fromVer == null)
        {
            throw new ArgumentException(“头顶点并不存在!”);
        }
        Vertex<T> toVer = Find(to); //找到截至顶点
澳门葡京备用网址,        if (toVer == null)
        {
            throw new ArgumentException(“尾顶点并不存在!”);
        }
        //无向边的五个极点都需记上边音讯
        AddDirectedEdge(fromVer, toVer);
        AddDirectedEdge(toVer, fromVer);
    }
    public bool Contains(T item) //查找图中是不是带有某项
    {
        foreach (Vertex<T> v in items)
        {
            if (v.data.Equals(item))
            {
                return true;
            }
        }
        return false;
    }
    private Vertex<T> Find(T item) //查找指定项并回到
    {
        foreach (Vertex<T> v in items)
        {
            if (v.data.Equals(item))
            {
                return v;
            }
        }
        return null;
    }
    //添加有向边
    private void AddDirectedEdge(Vertex<T> fromVer, Vertex<T> toVer)
    {
        if (fromVer.firstEdge == null) //无邻接点时
        {
            fromVer.firstEdge = new Node(toVer);
        }
        else
        {
            Node tmp, node = fromVer.firstEdge;
            do
            {   //检查是否添加了重复边
                if (node.adjvex.data.Equals(toVer.data))
                {
                    throw new ArgumentException(“添加了再一次的边!”);
                }
                tmp = node;
                node = node.next;
            } while (node != null);
            tmp.next = new Node(toVer); //添加到链表未尾
        }
    }
    public override string ToString() //仅用于测试
    {   //打印每个节点和它的邻接点
        string s = string.Empty;
        foreach (Vertex<T> v in items)
        {
            s += v.data.ToString() + “:”;
            if (v.firstEdge != null)
            {
                Node tmp = v.firstEdge;
                while (tmp != null)
                {
                    s += tmp.adjvex.data.ToString();
                    tmp = tmp.next;
                }
            }
            s += “\r\n”;
        }
        return s;
    }
    //嵌套类,表示链表中的表结点
    public class Node
    {
        public Vertex<T> adjvex; //邻接点域
        public Node next; //下一个邻接点指针域
        public Node(Vertex<T> value)
        {
            adjvex = value;
        }
    }
    //嵌套类,表示存放于数组中的表头结点
    public class Vertex<TValue>
    {
        public TValue data; //数据
        public Node firstEdge; //邻接点链表头指针
        public Boolean visited; //访问标志,遍历时使用
        public Vertex(电视alue value) //构造方法
        {
            data = value;
        }
    }
}

 

AdjacencyList<T>类使用泛型实现了图的邻接表存储结构。它涵盖五个里面类,Vertex<Tvalue>类(109~118行代码)用于表示一个表头结点,Node类(99~107)则用来表示表结点,其中存放着邻接点音信,用来表示表头结点的某条边。两个Node用next指针相连形成一个单链表,表头指针为Vertex类的firstEdge成员,表头结点所表示的终点的有着边的信息均包含在链表内,其协会如图8.12所示。所不同之处在于:

l         Vertex类中蕴藏了一个visited成员,它的职能是在图遍历时标识当前节点是否被访问过,这一点在稍后会讲到。

l         邻接点指针域adjvex直接指向某个表头结点,而不是表头结点在数组中的索引。

AdjacencyList<T>类中动用了一个泛型List代替数组来保存表头结点音信(第5行代码),从而不再考虑数组存储空间不够的动静暴发,简化了操作。

鉴于一条无向边的信息需要在边的三个终端分别存储新闻,即添加六个有向边,所以58~78行代码的私家方法AddDirectedEdge()方法用于添加一个有向边。新的邻接点音信即可以增长到链表的头部也得以加上到尾部,添加到链表头部可以简化操作,但考虑到要反省是否添加了双重边,需要遍历整个链表,所以最终把邻接点信息添加到链表尾部。

【例8-1 
Demo8-1.cs】图的邻接表存储结构测试

using System;
class Demo8_1
{
    static void Main(string[] args)
    {
        AdjacencyList<char> a = new AdjacencyList<char>();
        //添加顶点
        a.AddVertex(‘A’);
        a.AddVertex(‘B’);
        a.AddVertex(‘C’);
        a.AddVertex(‘D’);
        //添加边
        a.AddEdge(‘A’, ‘B’);
        a.AddEdge(‘A’, ‘C’);
        a.AddEdge(‘A’, ‘D’);
        a.AddEdge(‘B’, ‘D’);
        Console.WriteLine(a.ToString());
    }
}

运转结果:
 

A:BCD

B:AD

C:A

D:AB

 

本例存储的表如图8.12所示,结果中,冒号前面的是表头结点,冒号后边的是链表中的表结点。

在开端化的时候,链表头的prev和next都是指向自身的。

4. 简练算法

二分查找-递归方法
二分查找-非递归方法
冒泡排序

- (void)testBuble
{
    int temp;
    int array[8] = {1,24,56,34,67,78,324,43};
    for (int i = 0; i<8-1; i++) {
        for (int j = 0; j<8-1-i; j++) {
            if(array[j] > array[j + 1])
            {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }

        }
    }
    for (int i = 0; i<8; i++) {
        NSLog(@"%d",array[i]);
    }


    quickSort(array,0,7);

}

敏捷排序

//快速排序  调用方法  quickSort(a,0,n);  θ(nlogn)
void quickSort (int a[] , int low , int high)
{
    if (high < low + 2)
        return;
    int start = low;
    int end = high;
    int temp;

    while (start < end)
    {
        while ( ++start < high && a[start] <= a[low]);//找到第一个比a[low]数值大的位子start

        while ( --end  > low  && a[end]  >= a[low]);//找到第一个比a[low]数值小的位子end

        //进行到此,a[end] < a[low] < a[start],但是物理位置上还是low < start < end,因此接下来交换a[start]和a[end],于是[low,start]这个区间里面全部比a[low]小的,[end,hight]这个区间里面全部都是比a[low]大的

        if (start < end)
        {
            temp = a[start];
            a[start]=a[end];
            a[end]=temp;
        }
        //在GCC编译器下,该写法无法达到交换的目的,a[start] ^= a[end] ^= a[start] ^= a[end];编译器的问题
    }
    //进行到此,[low,end]区间里面的数都比a[low]小的,[end,higt]区间里面都是比a[low]大的,把a[low]放到中间即可

    //在GCC编译器下,该写法无法达到交换的目的,a[low] ^= a[end] ^= a[low] ^= a[end];编译器的问题

    temp = a[low];
    a[low]=a[end];
    a[end]=temp;

    //现在就分成了3段了,由最初的a[low]枢纽分开的
//    quickSort(a, low, end);
    quickSort(a, start, high);
    for (int i = 0; i<8; i++) {
        printf("\n%d\n",a[i]);
    }
}

8.3 图的遍历

和树的遍历类似,在此,大家意在从图中某一顶点出发访遍图中其它顶点,且使每一个终极仅被访问两次,这一经过就叫做图的遍历(TraversingGraph)。假使只访问图的顶点而不关注边的信息,那么图的遍历非常简短,使用一个foreach语句遍历存放顶点音信的数组即可。但万一为了促成特定算法,就需要基于边的音信依据一定顺序举办遍历。图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的功底。

图的遍历要比树的遍历复杂得多,由于图的任一顶点都可能和此外顶点相邻接,故在走访了某顶点之后,可能顺着某条边又走访到了已走访过的终极,由此,在图的遍历过程中,必须记录每个访问过的终端,以免同一个终极被访问多次。为此给顶点附设访问标志visited,其初值为false,一旦某个顶点被访问,则其visited标志置为true。

图的遍历方法有两种:一种是深度优先搜索遍历(Depth-First Search 简称DFS);另一种是广度优先搜索遍历(Breadth_First Search
简称BFS)。

  1. static inline void __list_add(struct list_head *new,  
  2.                   struct list_head *与数据结构,反转链表PHP实现。prev,  
  3.                   struct list_head *next)  
  4. {  
  5.     next->prev = new;  
  6.     new->next = next;  
  7.     new->prev = prev;  
  8.     prev->next = new;  
  9. }  
  10.   
  11. static inline void list_add(struct list_head *newstruct list_head *head)  
  12. {  
  13.     __list_add(new, head, head->next);  
  14. }  
  15.   
  16. static inline void list_add_tail(struct list_head *newstruct list_head *head)  
  17. {  
  18.     __list_add(new, head->prev, head);  
  19. }  
5. 架构

8.3.1  深度优先搜索遍历

图的纵深优先搜索遍历类似于二叉树的深度优先搜索遍历。其主干思想如下:假定以图中某个顶点Vi为着眼点,首先走访出发点,然后采用一个Vi的未访问过的邻接点Vj,以Vj为新的着眼点继续展开深度优先搜索,直至图中兼有终端都被访问过。显然,这是一个递归的探寻过程。

现以图8.15为例表明深度优先搜索过程。假定V1是观点,首先访问V1。因V1有五个邻接点V2、V3均末被访问过,可以挑选V2作为新的落脚点,访问V2之后,再找V2的末访问过的邻接点。同V2分界的有V1、V4和V5,其中V1已被访问过,而V4、V5从未被访问过,能够选用V4作为新的角度。重复上述搜索过程,继续依次访问V8、V5 。访问V5之后,由于与V5紧邻的终点均已被访问过,搜索退回到V8,访问V8的另一个邻接点V6。接下来依次访问V3和V7,最终收获的的终极的拜访体系为:V1 → V2 → V4 → V8 → V5 → V6 → V3 → V7

澳门葡京备用网址 5

 

下边依据上一节创制的邻接表存储结构充足深度优先搜索遍历代码。

【例8-2 
DFSTraverse.cs】深度优先搜索遍历

开辟【例8-1 
AdjacencyList.cs】,在AdjacencyList<T>类中添加以下代码后,将文件另存为DFSTraverse.cs。

35      public void DFSTraverse() //深度优先遍历
36      {
37          InitVisited(); //将visited标志全体置为false
38          DFS(items[0]); //从第一个顶峰最先遍历
39      }
40      private void DFS(Vertex<T> v) //使用递归举办深度优先遍历
41      {
42          v.visited = true; //将访问标志设为true
43          Console.Write(v.data + ” “); //访问
44          Node node = v.firstEdge;
45          while (node != null) //访问此顶点的享有邻接点
46          {   //倘若邻接点未被访问,则递归访问它的边
47              if (!node.adjvex.visited)
48              {
49                  DFS(node.adjvex); //递归
50              }
51              node = node.next; //访问下一个邻接点
52          }
53      }

98      private void InitVisited() //初始化visited标志
99      {
100         foreach (Vertex<T> v in items)
101         {
102             v.visited = false; //全体置为false
103         }
104     }

 

【例8-2 
Demo8-2.cs】深度优先搜索遍历测试

using System;
class Demo8_2
{
    static void Main(string[] args)
    {
        AdjacencyList<string> a = new AdjacencyList<string>();
        a.AddVertex(“V1”);
        a.AddVertex(“V2”);
        a.AddVertex(“V3”);
        a.AddVertex(“V4”);
        a.AddVertex(“V5”);
        a.AddVertex(“V6”);
        a.AddVertex(“V7”);
        a.AddVertex(“V8”);
        a.AddEdge(“V1”, “V2”);
        a.AddEdge(“V1”, “V3”);
        a.AddEdge(“V2”, “V4”);
        a.AddEdge(“V2”, “V5”);
        a.AddEdge(“V3”, “V6”);
        a.AddEdge(“V3”, “V7”);
        a.AddEdge(“V4”, “V8”);
        a.AddEdge(“V5”, “V8”);
        a.AddEdge(“V6”, “V8”);
        a.AddEdge(“V7”, “V8”);
        a.DFSTraverse();
    }
}

 

运行结果:

 

V1 V2 V4 V8 V5 V6 V3
V7

 

本例参照图8-15举办统筹,运行过程请参见对图8-15所作的分析。

双向循环链表的落实,很少有例外意况,基本都可以用公家的法门来处理。那里无论是加第一个节点,仍然此外的节点,使用的措施都一样。

8.3.2  广度优先搜索遍历

图的广度优先搜索遍历算法是一个支行遍历的历程,和二叉树的广度优先搜索遍历类同。它从图的某一顶点Vi起身,访问此顶点后,依次访问Vi的各样未曾访问过的邻接点,然后分别从这么些邻接点出发,直至图中具有已有已被访问的顶点的邻接点都被访问到。对于图8.15所示的无向连通图,若顶点Vi为发端访问的终端,则广度优先搜索遍历顶点访问顺序是:V1 → V2 → V3 → V4 → V5 → V6 → V7 → V8。遍历过程如图8.16的所示。

澳门葡京备用网址 6
 

和二叉树的广度优先搜索遍历类似,图的广度优先搜索遍历也需要依靠队列来完成,例8.3演示了这些进程。

【例8-3 
BFSTraverse.cs】广度优先搜索遍历

开辟【例8-2 
DFSTraverse.cs】,在AdjacencyList<T>类中添加以下代码后,将文件另存为BFSTraverse.cs。

54      public void BFSTraverse() //广度优先遍历
55      {
56          InitVisited(); //将visited标志全部置为false
57          BFS(items[0]); //从首个顶峰开头遍历
58      }
59      private void BFS(Vertex<T> v) //使用队列举行广度优先遍历
60      {   //创设一个行列
61          Queue<Vertex<T>> queue = new Queue<Vertex<T>>();
62          Console.Write(v.data + ” “); //访问
63          v.visited = true; //设置访问标志
64          queue.Enqueue(v); //进队
65          while (queue.Count > 0) //只要队不为空就循环
66          {
67              Vertex<T> w = queue.Dequeue();
68              Node node = w.firstEdge;
69              while (node != null) //访问此顶点的兼具邻接点
70              {   //假若邻接点未被访问,则递归访问它的边
71                  if (!node.adjvex.visited)
72                  {
73                      Console.Write(node.adjvex.data + ” “); //访问
74                      node.adjvex.visited = true; //设置访问标志
75                      queue.Enqueue(node.adjvex); //进队
76                  }
77                  node = node.next; //访问下一个邻接点
78              }
79          }
80      }

 

【例8-3 
Demo8-3.cs】广度优先搜索遍历测试

using System;
class Demo8_3
{
    static void Main(string[] args)
    {
        AdjacencyList<string> a = new AdjacencyList<string>();
        a.AddVertex(“V1”);
        a.AddVertex(“V2”);
        a.AddVertex(“V3”);
        a.AddVertex(“V4”);
        a.AddVertex(“V5”);
        a.AddVertex(“V6”);
        a.AddVertex(“V7”);
        a.AddVertex(“V8”);
        a.AddEdge(“V1”, “V2”);
        a.AddEdge(“V1”, “V3”);
        a.AddEdge(“V2”, “V4”);
        a.AddEdge(“V2”, “V5”);
        a.AddEdge(“V3”, “V6”);
        a.AddEdge(“V3”, “V7”);
        a.AddEdge(“V4”, “V8”);
        a.AddEdge(“V5”, “V8”);
        a.AddEdge(“V6”, “V8”);
        a.AddEdge(“V7”, “V8”);
        a.BFSTraverse(); //广度优先搜索遍历
    }
}

 

运转结果:

 

V1 V2 V3 V4 V5 V6 V7
V8

 

运作结果请参照图8.16拓展解析。

除此以外,链表API落实时大约都是分为两层:一层外部的,如list_add、list_add_tail,用来撤除一些例外情形,调用内部贯彻;一层是内部的,函数名前会加双下划线,如__list_add,往往是多少个操作公共的一些,或者免除例外后的落实。

8.3.3  非连通图的遍历

上述研讨的图的两种遍历方法都是相对于无向连通图的,它们都是从一个终极出发就能访问到图中的所有终端。若无向图是非连通图,则不得不访问到开头点所在连通分量中的所有终端,其他连接分量中的顶点是无法拜会到的(如图8.17所示)。为此需要从任何各样连通分量中拔取起头点,分别举行遍历,才可以访问到图中的所有终端,否则无法访问到拥有终端。为此如出一辙需要再选伊始点,继续展开遍历,直到图中的所有终端都被访问过停止。

澳门葡京备用网址 7
 

上例的代码只需对DFSTraverse()方法和BFSTraverse()方法稍作修改,便得以遍历非连通图。

 public void DFSTraverse() //深度优先遍历
    {
        InitVisited(); //将visited标志全体置为false
        foreach (Vertex<T> v in items)
        {
            if (!v.visited) //倘若未被访问
            {
                DFS(v); //深度优先遍历
            }
        }
    }
    public void BFSTraverse() //广度优先遍历
    {
        InitVisited(); //将visited标志全体置为false
        foreach (Vertex<T> v in items)
        {
            if (!v.visited) //如若未被访问
            {
                BFS(v); //广度优先遍历
            }
        }
    }

  1. static inline void __list_del(struct list_head * prev, struct list_head * next)  
  2. {  
  3.     next->prev = prev;  
  4.     prev->next = next;  
  5. }  
  6.   
  7. static inline void list_del(struct list_head *entry)  
  8. {  
  9.     __list_del(entry->prev, entry->next);  
  10.     entry->next = LIST_POISON1;  
  11.     entry->prev = LIST_POISON2;  
  12. }  
  13.   
  14. static inline void list_del_init(struct list_head *entry)  
  15. {  
  16.     __list_del(entry->prev, entry->next);  
  17.     INIT_LIST_HEAD(entry);  
  18. }  

list_del是链表中节点的去除。之所以在调用__list_del后又把被去除元素的next、prev指向特殊的LIST_POSITION1和LIST_POSITION2,是为着调节未定义的指针。

list_del_init则是删除节点后,随即把节点中指针再一次先导化,这种删除形式更是实用。

  1. static inline void list_replace(struct list_head *old,  
  2.                 struct list_head *new)  
  3. {  
  4.     new->next = old->next;  
  5.     new->next->prev = new;  
  6.     new->prev = old->prev;  
  7.     new->prev->next = new;  
  8. }  
  9.   
  10. static inline void list_replace_init(struct list_head *old,  
  11.                     struct list_head *new)  
  12. {  
  13.     list_replace(old, new);  
  14.     INIT_LIST_HEAD(old);  
  15. }  

list_replace是将链表中一个节点old,替换为另一个节点new。从实现来看,即便old所在地链表只有old一个节点,new也足以成功替换,这就是双向循环链表可怕的通用之处。

list_replace_init将被互换的old随即又先导化。

  1. static inline void list_move(struct list_head *list, struct list_head *head)  
  2. {  
  3.     __list_del(list->prev, list->next);  
  4.     list_add(list, head);  
  5. }  
  6.   
  7. static inline void list_move_tail(struct list_head *list,  
  8.                   struct list_head *head)  
  9. {  
  10.     __list_del(list->prev, list->next);  
  11.     list_add_tail(list, head);  
  12. }  

list_move的职能是把list节点从原链表中去除,并参加新的链表head中。

list_move_tail只在进入新链表时与list_move有所不同,list_move是加到head之后的链表头部,而list_move_tail是加到head在此之前的链表尾部。

  1. static inline int list_is_last(const struct list_head *list,  
  2.                 const struct list_head *head)  
  3. {  
  4.     return list->next == head;  
  5. }  

list_is_last 判断list是否处在head链表的尾巴。

  1. static inline int list_empty(const struct list_head *head)  
  2. {  
  3.     return head->next == head;  
  4. }  
  5.   
  6. static inline int list_empty_careful(const struct list_head *head)  
  7. {  
  8.     struct list_head *next = head->next;  
  9.     return (next == head) && (next == head->prev);  
  10. }  

list_empty 判断head链表是否为空,为空的意趣就是只有一个链表头head。

list_empty_careful 同样是判定head链表是否为空,只是检查更为严厉。

  1. static inline int list_is_singular(const struct list_head *head)  
  2. {  
  3.     return !list_empty(head) && (head->next == head->prev);  
  4. }  

list_is_singular
判断head中是不是只有一个节点,即除链表头head外只有一个节点。

  1. static inline void __list_cut_position(struct list_head *list,  
  2.         struct list_head *head, struct list_head *entry)  
  3. {  
  4.     struct list_head *new_first = entry->next;  
  5.     list->next = head->next;  
  6.     list->next->prev = list;  
  7.     list->prev = entry;  
  8.     entry->next = list;  
  9.     head->next = new_first;  
  10.     new_first->prev = head;  
  11. }  
  12.   
  13. static inline void list_cut_position(struct list_head *list,  
  14.         struct list_head *head, struct list_head *entry)  
  15. {  
  16.     if (list_empty(head))  
  17.         return;  
  18.     if (list_is_singular(head) &&  
  19.         (head->next != entry && head != entry))  
  20.         return;  
  21.     if (entry == head)  
  22.         INIT_LIST_HEAD(list);  
  23.     else  
  24.         __list_cut_position(list, head, entry);  
  25. }  

list_cut_position
用于把head链表分为六个部分。从head->next一贯到entry被从head链表中除去,参预新的链表list。新链表list应该是空的,或者原来的节点都得以被忽视掉。可以看来,list_cut_position中清除了一部分意料之外意况,保证调用__list_cut_position时至少有一个要素会被参加新链表。

  1. static inline void __list_splice(const struct list_head *list,  
  2.                  struct list_head *prev,  
  3.                  struct list_head *next)  
  4. {  
  5.     struct list_head *first = list->next;  
  6.     struct list_head *last = list->prev;  
  7.   
  8.     first->prev = prev;  
  9.     prev->next = first;  
  10.   
  11.     last->next = next;  
  12.     next->prev = last;  
  13. }  
  14.   
  15. static inline void list_splice(const struct list_head *list,  
  16.                 struct list_head *head)  
  17. {  
  18.     if (!list_empty(list))  
  19.         __list_splice(list, head, head->next);  
  20. }  
  21.   
  22. static inline void list_splice_tail(struct list_head *list,  
  23.                 struct list_head *head)  
  24. {  
  25.     if (!list_empty(list))  
  26.         __list_splice(list, head->prev, head);  
  27. }  

list_splice的效用和list_cut_position正相反,它合并两个链表。list_splice把list链表中的节点参加head链表中。在实际操作在此之前,要先判断list链表是否为空。它保证调用__list_splice时list链表中最少有一个节点可以被联合到head链表中。

list_splice_tail只是在集合链表时插入的职位不同。list_splice是把原本list链表中的节点全加到head链表的头部,而list_splice_tail则是把原来list链表中的节点全加到head链表的尾巴。

  1. static inline void list_splice_init(struct list_head *list,  
  2.                     struct list_head *head)  
  3. {  
  4.     if (!list_empty(list)) {  
  5.         __list_splice(list, head, head->next);  
  6.         INIT_LIST_HEAD(list);  
  7.     }  
  8. }  
  9.   
  10. static inline void list_splice_tail_init(struct list_head *list,  
  11.                      struct list_head *head)  
  12. {  
  13.     if (!list_empty(list)) {  
  14.         __list_splice(list, head->prev, head);  
  15.         INIT_LIST_HEAD(list);  
  16.     }  
  17. }  

list_splice_init
除了成功list_splice的效果,还把变空了的list链表头重新先河化。

list_splice_tail_init
除了成功list_splice_tail的意义,还吧变空了得list链表头重新起头化。

list操作的API大致如以上所列,包括链表节点添加与删除、节点从一个链表转移到另一个链表、链表中一个节点被替换为另一个节点、链表的集合与拆分、查看链表当前是不是为空或者唯有一个节点。接下来,是操作链表遍历时的部分宏,大家也简要介绍一下。

  1. #define list_entry(ptr, type, member) \
      
  2.     container_of(ptr, type, member)  

list_entry紧要用以从list节点查找其内嵌在的结构。比如定义一个构造struct
A{ struct list_head list; };
假设知道结构中链表的地址ptrList,就足以从ptrList进而获取整个结构的地点(即一切结构的指针)
struct A *ptrA = list_entry(ptrList, struct A, list);

这种地点翻译的技能是linux的拿手好戏,container_of随处可见,只是链表节点多被封装在更复杂的协会中,使用专门的list_entry定义也是为着使用方便。

  1. #define list_first_entry(ptr, type, member) \
      
  2.     list_entry((ptr)->next, type, member)  

list_first_entry是将ptr看完一个链表的链表头,取出其中第一个节点对应的结构地址。使用list_first_entry是应确保链表中最少有一个节点。

  1. #define list_for_each(pos, head) \
      
  2.     for (pos = (head)->next; prefetch(pos->next), pos != (head); \  
  3.             pos = pos->next)  

list_for_each循环遍历链表中的每个节点,从链表头部的首先个节点,一直到链表尾部。中间的prefetch是为着利用阳台特色加速链表遍历,在某些平台下定义为空,可以忽略。

  1. #define __list_for_each(pos, head) \
      
  2.     for (pos = (head)->next; pos != (head); pos = pos->next)  

__list_for_each与list_for_each没什么不同,只是少了prefetch的始末,实现上尤其简易易懂。

  1. #define list_for_each_prev(pos, head) \
      
  2.     for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \  
  3.             pos = pos->prev)  

list_for_each_prev与list_for_each的遍历顺序相反,从链表尾逆向遍历到链表头。

  1. #define list_for_each_safe(pos, n, head) \
      
  2.     for (pos = (head)->next, n = pos->next; pos != (head); \  
  3.         pos = n, n = pos->next)  

list_for_each_safe
也是链表顺序遍历,只是更加安全。尽管在遍历过程中,当前节点从链表中剔除,也不会潜移默化链表的遍历。参数上需要加一个暂存的链表节点指针n。

  1. #define list_for_each_prev_safe(pos, n, head) \
      
  2.     for (pos = (head)->prev, n = pos->prev; \  
  3.          prefetch(pos->prev), pos != (head); \  
  4.          pos = n, n = pos->prev)  

list_for_each_prev_safe
与list_for_each_prev同样是链表逆序遍历,只是加了链表节点删除珍贵。

  1. #define list_for_each_entry(pos, head, member)              \
      
  2.     for (pos = list_entry((head)->next, typeof(*pos), member);   \  
  3.          prefetch(pos->member.next), &pos->member != (head);  \  
  4.          pos = list_entry(pos->member.next, typeof(*pos), member))  

list_for_each_entry不是遍历链表节点,而是遍历链表节点所嵌套进的布局。那一个实现上相比复杂,但足以等价于list_for_each加上list_entry的组合。

  1. #define list_for_each_entry_reverse(pos, head, member)          \
      
  2.     for (pos = list_entry((head)->prev, typeof(*pos), member);   \  
  3.          prefetch(pos->member.prev), &pos->member != (head);  \  
  4.          pos = list_entry(pos->member.prev, typeof(*pos), member))  

list_for_each_entry_reverse
是逆序遍历链表节点所嵌套进的协会,等价于list_for_each_prev加上list_etnry的组合。

  1. #define list_for_each_entry_continue(pos, head, member)         \
      
  2.     for (pos = list_entry(pos->member.next, typeof(*pos), member);   \  
  3.          prefetch(pos->member.next), &pos->member != (head);  \  
  4.          pos = list_entry(pos->member.next, typeof(*pos), member))  

list_for_each_entry_continue也是遍历链表上的节点嵌套的布局。只是不要从链表头开端,而是从结构指针的下一个构造开头,一贯到链表尾部。

  1. #define list_for_each_entry_continue_reverse(pos, head, member)     \
      
  2.     for (pos = list_entry(pos->member.prev, typeof(*pos), member);   \  
  3.          prefetch(pos->member.prev), &pos->member != (head);  \  
  4.          pos = list_entry(pos->member.prev, typeof(*pos), member))  

list_for_each_entry_continue_reverse
是逆序遍历链表上的节点嵌套的构造。只是不要从链表尾起始,而是从社团指针的前一个协会开端,平素到链表头部。

  1. #define list_for_each_entry_from(pos, head, member)             \
      
  2.     for (; prefetch(pos->member.next), &pos->member != (head);    \  
  3.          pos = list_entry(pos->member.next, typeof(*pos), member))  

list_for_each_entry_from
是从当前布局指针pos开头,顺序遍历链表上的布局指针。

  1. #define list_for_each_entry_safe(pos, n, head, member)          \
      
  2.     for (pos = list_entry((head)->next, typeof(*pos), member),   \  
  3.         n = list_entry(pos->member.next, typeof(*pos), member);  \  
  4.          &pos->member != (head);                     \  
  5.          pos = n, n = list_entry(n->member.next, typeof(*n), member))  

list_for_each_entry_safe
也是逐两遍历链表上节点嵌套的布局。只是加了删除节点的护卫。

  1. #define list_for_each_entry_safe_continue(pos, n, head, member)         \
      
  2.     for (pos = list_entry(pos->member.next, typeof(*pos), member),       \  
  3.         n = list_entry(pos->member.next, typeof(*pos), member);      \  
  4.          &pos->member != (head);                     \  
  5.          pos = n, n = list_entry(n->member.next, typeof(*n), member))  

list_for_each_entry_safe_continue
是从pos的下一个布局指针起初,顺序遍历链表上的结构指针,同时加了节点删除保护。

  1. #define list_for_each_entry_safe_from(pos, n, head, member)             \
      
  2.     for (n = list_entry(pos->member.next, typeof(*pos), member);     \  
  3.          &pos->member != (head);                     \  
  4.          pos = n, n = list_entry(n->member.next, typeof(*n), member))  

list_for_each_entry_safe_from
是从pos起初,顺序遍历链表上的结构指针,同时加了节点删除珍重。 

  1. #define list_for_each_entry_safe_reverse(pos, n, head, member)      \
      
  2.     for (pos = list_entry((head)->prev, typeof(*pos), member),   \  
  3.         n = list_entry(pos->member.prev, typeof(*pos), member);  \  
  4.          &pos->member != (head);                     \  
  5.          pos = n, n = list_entry(n->member.prev, typeof(*n), member))  

list_for_each_entry_safe_reverse
是从pos的前一个结构指针先导,逆序遍历链表上的构造指针,同时加了节点删除珍视。

由来截至,咱们介绍了linux中双向循环链表的协会、所有的操作函数和遍历宏定义。相信之后在linux代码中相遇链表的施用,不会再陌生。

澳门葡京备用网址 8

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website