哨兵在链表中的使用

来源:互联网 发布:国外自助游软件 编辑:程序博客网 时间:2024/05/17 13:09

      首先我想写这篇文章的原因是我用google搜索,但是没有找到很多有关哨兵在链表中的使用,如果有的话,也只是轻描淡写的写了写代码,没有明确说明使用哨兵的好处,这篇文章说明了用哨兵实现有序和无序链表的好处。

首先定义一个节点的结构.

struct Node {    int value;    Node* next;    Node() : value(0), next(0) {}    Node(int v, Node* n) : value(v), next(n) {}};

     通常我们写链表插入函数的时候,要分两种情况:

     (1)链表头为空 

     (2)链表头不为空

     如果为空的话,函数就需要改变Head指向的节点,因此,如果insert函数如果有head参数的数的话,那就得必须是head的引用(Node* &head)或指向head的指针(Node** head),这样才能处理链表为空的情况(当然,如果你把head指针设置为全局的话,那就可以不必传参了,一般也不会这样做,否则要为每个链表写个insert函数),如果不用哨兵的话,通常的代码是这样的。

bool insert(Node** head, int value) {    if (*head == NULL) {        *head = new Node(value, 0);        return true;    }    Node** current = head;    while ((*current)->next != NULL) {        current = &(*current)->next;    }    (*current)->next = new Node(value, 0);    return true;}

开头你就得判断head是不是为NULL,当要插入的数据很多的时候,这无疑是一笔很大的开销。如果不为空的话,传入的head也就可以不必为引用了,上面的例子是链表为空和链表不为空了都是用引用(这里就不区别指针和引用了)处理的。看下面不用head引用来处理链表非空情况:

bool insert(Node** head, int value) {    if (*head == NULL) {        *head = new Node(value, 0);        return true;    }    Node* current = *head;    while (current->next != NULL) {        current = current->next;    }    current->next = new Node(value, 0);    return true;}

这里之所以行得通,是因为    current->next = new Node(value, 0)  , 这种形式,如果while循环中的条件改为  while (current != NULL)  ,后面改为  current = new Node(value, 0)   这样就不行了(其实很多人还是会写出这种代码的,我也写过,结果就是找不出问题在哪里)。但是链表非空用head引用去这样处理是没问题的,也就是while条件为  while(*current != NULL)   ,后面  *current = new Node(value, 0)  ,原因是因为这里是引用,可以改变next指针的值。对于指针可能这里还是比较难理解,举个例子吧:

    int* a = new int(2);    int* b = a;    b = new int(3);

这个a地址存放的值是2,而不是3,没有改变a地址存放的值

    int* a = new int(2);    int** b = &a;    *b = new int(3);

这个a地址存放的值就是3,而不是原来的2了,懂了这个,也就可以明白上面那个是怎么回事了。

      扯了好久,都没有扯上哨兵,这里引入哨兵的目的就是要解决每次判断head是否为空的问题,这样想吧,如果我让一个空的链表含有一个元素(哨兵元素),这样是不就不用判断head为空的情况了,那么哨兵的初始值为多少好呢?如果是无序的链表的话,当然就不用考虑哨兵初始值的问题,插入到队尾就行了(如果无序的话,插入到开始不是更好吗?我也不知道原因,这里就用链表看作是队列吧,队列就是插入到尾部)。看代码

bool insert(Node* head, int value) {    while (head->next != NULL) {        head = head->next;    }  head->next = new Node(value, 0);    return true;}

这样head也就不需要传引用了,但是初始化head的时候必须让他指向一个哨兵节点,如果为head指向为空insert会崩掉。

     下面在说说有序链表的问题,有序链表的话,插入新节点的位置就不一定是在末尾了,可能在头部,中间,尾部,似乎要分很多情况,我自己尝试着不用哨兵节点,结果开头的空指针判断不可少,while循环还得加一个判断条件类似  (current->next != NULL && current->value < value)  这样子,这样的while循环多了一个条件无疑增加了开销,这时哨兵的可以派上用场了,一开始我就让空的链表含有两个元素分别是:Node* MAX = new Node(10000, 0),  Node* MIN = new Node(-10000, MAX), (用10000表示数很大), head = MIN。insert函数为:

bool insert(Node* head, int value) {    Node* current = head;    Node* pre = head;    while (current->value < value) {        pre = current;        current = current->next;    }  pre->next = new Node(value, current);    return true;}

这样的代码可谓是简洁明了吧,这就是哨兵的好处。如果你要写print函数的话,根据你用的哨兵的情况的来写就好了。

其实这种尾部放哨兵的方法还可以用于数组,这里也附带说一下,如果要在含n个数的数组array中找个数value,一般是这样写:

for (int i = 0; i < n; ++i) {    if (value == array[i])        return i;}

用了哨兵的方式为:令 array[n] = value;然后代码为:

for (int i = 0;; i++) {    if (value == array[i]) {        if (i != n)            return i;    }}

好处在哪显而易见。

0 0
原创粉丝点击