哨兵在链表中的使用
来源:互联网 发布:国外自助游软件 编辑:程序博客网 时间: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; }}
好处在哪显而易见。
- 哨兵在链表中的使用
- 排序中的哨兵
- 数据结构中的哨兵
- 算法----带哨兵的双向链表
- 带哨兵的双向链表
- 不带哨兵的双向链表
- 带哨兵的环形双向链表
- 带有哨兵的双向循环链表
- 使用带sentinel哨兵的双向链表实现栈和队列
- 哨兵的使用
- 【算法导论】10.2不带哨兵节点和带哨兵节点的双向链表
- 哨兵
- 使用“哨兵”减小时间复杂度
- C++实现有哨兵的双向循环链表
- 数据结构--双向带哨兵的循环链表
- 10.2-5 带哨兵的单向循环链表
- 静态查找表 哨兵的使用与一般的对比(简化版)
- 归并排序(使用哨兵元素)
- Android Connection Refused问题处理
- HTTPD SSL mod_ssl mod_nss
- Android L SDK -- 一些有意思的新特性
- MyBatis的动态SQL详解
- 仁宝招聘,月入4000不是问题,包吃包住,美女众多!
- 哨兵在链表中的使用
- 黑马程序员——高新技术——类加载器,内省,注解,动态代理
- 变量范围
- 不同颜色对应的RGB值
- onchange事件与onpropertychange事件的区别
- 集合变数组
- 让我们一起来做一个好的Banner图片。
- 在AIX上运行RAC时网络方面的一些最佳经验
- Android中创建简单的视图分隔符