《C与指针》第十二章练习
本章例程
//12.3
#include <stdio.h>
#include <stdlib.h> typedef struct NODE{
struct NODE *link;
int value;
}Node; #define FALSE 0
#define TRUE 1 int sll_insert(register Node **linkp, int new_value)
{
register Node *current;
register Node *new; while((current = *linkp) != NULL && current->value < new_value)
linkp = ¤t->link; new = (Node *)malloc(sizeof(Node));
if(new == NULL)
return FALSE;
new->value = new_value; new->link = current;
*linkp = new;
return TRUE;
} //12.7
int dll_insert(register Node *rootp, int value)
{
register Node *this;
register Node *next;
register Node *newnode; for(this = rootp; (next = this->fwd) != NULL; this = next){
if(next->value == value)
return ;
if(next->value > value)
break;
} newnode = (Node *)malloc(sizeof(Node));
if(newnode == NULL)
return -;
newnode->value = value; newnode->fwd = next;
this->fwd = newnode;
if(this != rootp)
newnode->bwd = this;
else
newnode->bwd = NULL; if(next != NULL)
next->bwd = newnode;
else
rootp->bwd = newnode;
return ;
}
本章问题
1.程序12.3是否能进行改写,不使用current变量?如果可以,把你的答案和原先的函数作一比较。
answer:
int sll_insert(register Node **linkp, int new_value)
{
register Node *new;
while(*link != NULL && (*linkp)->value < new_value)
linkp = &(*linkp)->link; new = (Node *)malloc(sizeof(Node));
if(new == NULL)
return ;
new->value = new_value; new->link = *linkp;
*linkp = new;
return ;
}
This version of the program uses one fewer variable,but it has three extra indirections so it will take slightly(稍微) longer to execute,It is also harder to understand,which is its major drawback.
(这个程序的版本使用了更少的变量,不过它增加了三个额外的间接访问,所以它执行的时候要更长一些,它最大的缺点就是它的可读性更差)
2.有些数据结构课本建议在单链表中使用“头节点”,这个哑节点始终是链表的第一个元素,这就消除了插入到链表起始位置这个特殊状况,讨论这个技巧的利与弊。
answer:
(和不用处理任何特殊情况代码的sll_insert函数相比,这种使用头节点的技巧没有任何优越之处,而且自相矛盾的是,这个声称用于消除特殊情况的技巧实际将引入用于处理特殊情况的代码,当链表被创建时,必须添加哑节点,其他操纵这个链表的函数必须跳过这个哑节点,最后,这个哑节点还会浪费空间)
3.在程序12.3中,插入函数会把重复的值插入到什么位置?如果把比较操作符由<改为<=会有什么效果?
answer:Ahead of other nodes with the same value,if the comparison were changed,duplicate values would be inserted after other nodes with the same value.
(相同节点之前,如果条件发生改变,重复的值则插入在相同的值之后)
4.讨论一些技巧,怎样省略双链表中根节点的值字段?
answer:
如果根节点是动态分配内存的,我们可以通过只为节点的一部分分配内存来达到目的。
一种更安全的声明一个只包含指针的结构。根指针就是这类结构之一,每个节点只包含这类结构中的一个,这种方法的有趣之处在于结构之间的相互依赖,每个结构都包含了一个对方类型的字段。这种相互依赖性就在声明它们时产生了一个“先有鸡还是先有蛋”的问题:哪个结构先声明呢?这个问题只是通过其中一个结构标签的不完整声明来解决。
5.如果程序12.7中对malloc的调用在函数的起始部分执行会有什么结果?
answer: Each attempt to add a duplicate(重复的) value to the list would result in a memory leak:A new node would be allocated,but not add to the list.
(每尝试添加一个重复的值都会使内存泄露,一个新的节点被分配,但是没有添加到链表中)
6.能不能对一个无序的单链表进行排序?
answer:Yes,but it is very inefficient .the simplest strategy(策略) is to take the nodes off the list one by one and insert them into a new,ordered list.
(可以,不过相当低效,最简单的策略是把它们一个一个插入一个已经排好序的链表之中)
7.索引表是一种字母链表,表中的节点是出现于一本书或一篇文章中的单词,你可以使用一个有序的字符串单链表实现索引表,使用插入函数时不插入重复的单词,和这种实现方法有关的问题是搜索链表的时间将随着链表规模的扩大而急剧增长。
图12.1说明了另一种存储索引表的数据结构,它的思路是把一个大型的链表分解成26个小型链表--每个链表中的所有单词都以同一个字母开头,最初链表中的每个节点包含了一个字母和一个指向有序的以该字母开头的单词的单链表(以字符串的形式存储)的指针。
使用这种数据结构,搜索一个特定的单词所花费的时间与使用一个存储所有单词的单链表相比,有没有什么变化?
answer:
在多个链表的方案中进行查找比在一个包含所有单词的链表中查找效率要高得多,例如,查找一个以字母b开头的单词,我们就不需要在那些以a开头的子母中进行查找,在26个字母中,如果每个字母开头的单词出现的频率相同,这种多个链表方案的效率几乎可以提高26倍,不过实际改进的幅度要比这小一些。
本章练习
1.编写一个函数,用于计数一个单链表的节点个数,它的唯一参数是一个指向链表第一个节点的指针,编写这个函数时,你必须知道哪些信息?这个函数还能用于执行其他任务吗?
answer:
//自己的答案
int countnode(type *p)
{
int count = ;
while(p != NULL){
count++;
p = p->link;
}
return count;
}
这个函数很简单,虽然它只能用于它被声明的那种类型的节点--你必须知道节点的内部结构,下一张将讨论解决这个问题的技巧。
如果这个函数被调用时传递给它的指针是一个指向链表中间位置某个节点的指针,那么它将对链表中这个节点以后的节点进行计数。
2.编写一个函数,在一个无序的单链表中寻找一个特定的值,并返回一个指向该节点的指针。你可以假设节点数据结构在头文件singly_linked_list_node.h中定义。如果想让这个函数适用于有序的单链表,需不需要对它做些修改?
answer:
#include <stdio.h>
#include <singly_linked_list_node.h> Node *search(Node *head, type value)
{
Node *p = head;
while(p != NULL){
if(p->data == value)
break;
p = p->link;
}
return p;
}
Technically,no change is required to search an ordered list,though the function can be made more efficient with a minor change,if nodes are found whose value are greater than the desired value,there is no need to continue searching,this is implemented by changing the test in the for loop to
p != NULL && p->data <= value
(从技术上说,在一个有序链表中查找是不需要作什么改变的,不过如果稍微做一点改变将会更高效,如果节点找到一个值比期望的值还要大,就不需要继续查找了,可以把循环语句中的测试部分改为...)
3.重新编写程序12.7的dll_list函数,使头和尾指针分别以一个单独的指针传递给函数,而不是作为一个节点的一部分,从函数的逻辑而言,这个改动有何效果?
answer:
//使用头尾指针指向第一个和最后一个节点,指针类型为Node *,链表为空时,两个指针都指向空
#include <stdio.h>
#include <stdlib.h>
#include "double_linked_list_node.h" int dll_insert(Node *head, Node *rear, int value)
{
register Node *this;
register Node *next;
register Node *new; if(head != NULL){
for(this = head; (next = this->fwd) != NULL; this = next){
if(value == next->value)
return ;
if(next->value > value)
break;
}
}else{
this = next = NULL;
} new = (Node *)malloc(sizeof(Node));
if(new == NULL)
return -;
new->value = value; if(this == head)
head = new;
else
this->fwd = new; if(head == NULL || next == NULL)
rear = new;
else
next->bwd = new; new->fwd = next;
new->bwd = this;
return ;
}
this makes the function more complex,primarily because the root pointers can no longer be manipulated in the same way as the node pointers.
(使得函数更加复杂,根指针不再以节点指针的方式进行操作)
//标准答案,头尾指针分别为指向第一个元素fwd和最后一个元素的bwd
int dll_insert(Node **frontp, Node **rearp, int value)
{
register Node **fwdp;
register Node *next;
register Node *newnode;
fwdp = frontp;
while((next = *fwdp) != NULL){
if(next->value == value)
return ;
if(next->value > value)
break;
fwdp = &next->fwd;
} newnode = (Node *)malloc(sizeof(Node));
if(newnode == NULL)
return -;
newnode->value = value; newnode->fwd = next;
*fwdp = nexnode;
if(fwdp != frontp)
if(next != NULL)
newnode->bwd = next->bwd;
else
newnode->bwd = *rearp;
else
newnode->bwd = NULL;
if(next != NULL)
next->bwd = newnode;
else
*rearp = newnode;
return ;
}
4.编写一个函数,反序排列一个单链表中所有的节点。函数应该具有下面的原型:
struct NODE *sll_reverse(struct NODE *first);
在头文件singly_linked_list_node.h中声明节点数据结构。函数的参数指向链表的第一个节点。当链表被重排之后,函数返回一个指向链表新头节点的指针,链表最后一个节点的link字段的值应设置为NULL,在空链表(first = = NULL)上执行这个函数将返回NULL。
answer:
//方法一
#include <stdio.h>
#include <stdlib.h>
#include <singly_linked_list_node.h> struct NODE *sll_reverse(struct NODE *first)
{
Node *newfirst;
Node *p = first;
Node *q = first;
if(first == NULL)
return NULL; //让p指向最后一个元素,q指向倒数第二个
while(p->link != NULL){
q = p;
p = p->link;
}
newfirst = p; //箭头反转
while(q != first){
p->link = q;
p = q;
}
q->link = NULL;
return newfirst;
}
//方法二
#include <stdio.h>
#include <stdlib.h>
#include <singly_linked_list_node.h> //直接进行反转
struct NODE *sll_reverse(struct NODE *first)
{
Node *current;
Node *next; if(first != NULL){
for(current = NULL; first != NULL; first = next){
next = first->link;
first->link = current;
current = first;
}
} return first;
}
5.编写一个程序,从一个单链表中移除一个节点,函数的原型如下:
int sll_remove(struct NODE **rootp, struct NODE *node);
你可以假设节点数据结构在头文件singly_linked_list_node.h中定义,函数的第一个参数是一个指向链表根指针的指针,第二个参数是一个指向欲移除的节点的指针,如果链表并不包含该指针,函数就返回假,否则它就移除这个节点并返回真,把一个欲移除的节点的指针而不是欲移除的值作为参数传递给函数有哪些优点?
answer:
首先,接受一个指向我们希望删除的节点的指针可以使函数和存储在链表中的类型无关,相同的代码可以作用于不同类型的值,另一方面,如果我们并不知道哪个节点包含了需要被删除的值,我们首先必须对它进行查找。
#include <stdio.h>
#include <stdlib.h>
#include <singly_linked_list_node.h> int sll_remove(struct NODE **rootp, struct NODE *node)
{
Node *p;
assert(node != NULL);
while((p = *rootp) != NULL){
if(p == node){
*rootp = p->link;
free(node);
return ;
}
rootp = &p->link;
}
return ;
}
注意让这个函数用free函数删除节点会限制它只能适用动态分配节点的链表,另一种方案是如果函数返回真,由调用函数负责删除节点,当然如果调用程序没有删除动态分配的节点,将导致内存泄露。
6.编写一个程序,从双链表中移除一个节点,函数的原型如下
int dll_remove(struct NODE *rootp, struct NODE *node);
你可以假设节点数据结构在头文件double_linked_list_node.h文件中定义,函数第一个参数是一个指向包含链表根指针的节点的指针(和程序12.7相同),第二个参数是个指向欲移除的节点的指针,如果链表并不包含欲移除的指针,函数就返回假,否则函数移除该节点并返回真。
answer:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <doubly_linked_list_node.h> int dll_remove(struct NODE *rootp, struct NODE *node)
{
Node *p;
assert(node != NULL);
while((p = rootp->fwd) != NULL){
if(p == node){
if(p->bwd == NULL)
rootp->fwd = p->fwd;
else
p->fwd->bwd = p->bwd; if(p->fwd != NULL)
p->fwd->bwd = rootp;
else
rootp->bwd = p->bwd;
free(node);
return ;
}
rootp = p;
}
return ;
}
7.编写一个函数,把一个新单词插入到问题7所描述的索引表中。函数接受两个参数,一个指向list指针的指针和一个字符串,该字符串假定包含单个单词,如果这个单词原先并未存在索引表中,它应该复制一块动态分配的节点并插入到这个索引表中,如果成功插入返回真,如果该字符原先已经存在于索引表中,或字符串不是以字符开头或其他错误返回假。函数应该维护一个一级链表,节点的排序以字母为序,其余的二级链表则以单词为序排列。
answer:
//对这个链表比较感兴趣,所以把创建链表以及插入的整个过程都写下来
#include <stdio.h>
#include <stdlib.h>
#include <string.h> struct FIRSTNODE; //单词结构
typedef struct SECONDNODE{
char *string;
struct SECONDNODE *next;
}SecondNode; //字母索引结构
typedef struct FIRSTNODE{
char ch;
struct FIRSTNODE *alpha;
struct SECONDNODE *word;
}FirstNode; char delta[] = "abcdefghijklmnopqrstuvwxyz"; //在字母索引结构的尾部插入新的结构
FirstNode* insert_end(FirstNode *list, char ch)
{
FirstNode *newnode = (FirstNode *)malloc(sizeof(FirstNode));
if(newnode == NULL)
return NULL;
newnode->ch = ch;
newnode->alpha = NULL;
newnode->word = NULL;
if(list == NULL)
list = newnode;
else{
FirstNode *p;
p = list;
while(p->alpha != NULL)
p = p->alpha; p->alpha = newnode;
}
return list;
} //创建一个新的索引链表
FirstNode* createlist()
{
int i;
int len = strlen(delta);
FirstNode *list = NULL; for(i = ; i < len; i++)
list = insert_end(list,delta[i]); return list;
} //打印索引链表
void PrintNode(FirstNode *list)
{
FirstNode *p = list;
while(p->alpha != NULL){
printf("%c\n",p->ch);
if(p->word != NULL){
SecondNode *q = p->word;
while(q != NULL){
printf("->%s\n",q->string);
q = q->next;
}
}
printf("--------\n");
p = p->alpha;
}
} //插入新的单词
int insert_word(FirstNode **listp, char *str)
{
char c = *str;
FirstNode *p = *listp;
if(listp == NULL || str == NULL)
return ; while(p != NULL){
if(p->ch == c)
break;
p = p->alpha;
}
if(p == NULL)
return ; SecondNode *q = p->word;
SecondNode *s = NULL;
while(q != NULL){
if(strcmp(q->string,str) == )
return ;
if(strcmp(q->string,str) > )
break;
s = q;
q = q->next;
} SecondNode *newnode = (SecondNode *)malloc(sizeof(SecondNode));
if(newnode == NULL)
return ;
newnode->string = str;
//strcpy(newnode->string,str); error newnode->next = q;
if(s == NULL)
p->word = newnode;
else
s->next = newnode; return ;
} int main()
{
FirstNode *list = createlist();
insert_word(&list,"b2");
insert_word(&list,"b4");
insert_word(&list,"b1");
insert_word(&list,"b3");
PrintNode(list);
free(list);
return ;
}
运行结果:
...
上面的代码是用指向节点的指针来完成,要考虑特殊情况,而下面标准答案里的实现则是用指向结构指针的指针来完成,不需要考虑特殊情况。
《C与指针》第十二章练习的更多相关文章
- C和指针 第十二章 使用结构和指针
链表是一种常用的数据结构,每个节点通过链或者指针链接在一起,程序通过间接指针访问链表中的节点. typedef struct Node { //指向下一个节点的指针 struct Node *next ...
- C和指针 第十二章 结构体 习题
12.3 重新编写12.7,使用头和尾指针分别以一个单独的指针传递给函数,而不是作为一个节点的一部分 #include <stdio.h> #include <stdlib.h> ...
- C和指针 第十二章 使用结构和指针 双链表和语句提炼
双链表中每个节点包含指向当前和之后节点的指针,插入节点到双链表中需要考虑四种情况: 1.插入到链表头部 2.插入到链表尾部 3.插入到空链表中 4.插入到链表内部 #include <stdio ...
- C和指针 第十二章 结构体 整体赋值 error: expected expression
定义结构体后整体赋值时发生错误 typedef struct NODE { struct NODE *fwd; struct NODE *bwd; int value; } Node; //声明变量 ...
- perl5 第十二章 Perl5中的引用/指针
第十二章 Perl5中的引用/指针 by flamephoenix 一.引用简介二.使用引用三.使用反斜线(\)操作符四.引用和数组五.多维数组六.子程序的引用 子程序模板七.数组与子程序八.文件句 ...
- C和指针 (pointers on C)——第十二章:利用结构和指针
第十二章 利用结构和指针 这章就是链表.先单链表,后双向链表. 总结: 单链表是一种使用指针来存储值的数据结构.链表中的每一个节点包括一个字段,用于指向链表的下一个节点. 有一个独立的根指针指向链表的 ...
- [CSAPP笔记][第十二章并发编程]
第十二章 并发编程 如果逻辑控制流在时间上是重叠,那么它们就是并发的(concurrent).这种常见的现象称为并发(concurrency). 硬件异常处理程序,进程和Unix信号处理程序都是大家熟 ...
- C primer plus 第五版十二章习题
看完C prime plus(第五版)第十二章,随带完成了后面的习题. 1.不使用全局变量,重写程序清单12.4的程序. 先贴出12.4的程序,方便对照: /* global.c --- 使用外部变量 ...
- 《OpenCL异构并行编程实战》补充笔记散点,第五至十二章
▶ 第五章,OpenCL 的并发与执行模型 ● 内存对象与上下文相关而不是与设备相关.设备在不同设备之间的移动如下,如果 kernel 在第二个设备上运行,那么在第一个设备上产生的任何数据结果在第二个 ...
- 第十二章 Python文件操作【转】
12.1 open() open()函数作用是打开文件,返回一个文件对象. 用法格式:open(name[, mode[, buffering[,encoding]]]) -> file obj ...
随机推荐
- 打造基于CentOS7的xfce最简工作环境
参考这里:http://blog.csdn.net/smstong/article/details/44802989没成功. 又参考这里http://jensd.be/125/linux/rhel/i ...
- 让powershell同时只能运行一个脚本(进程互斥例子)
powershell,mutex,互斥,进程互斥,脚本互斥 powershell脚本互斥例子,在powershell类别文章中,声明原创唯一. powershell 传教士 原创文章 2016-07- ...
- 进程间通信--pipe
管道的两种局限性: 历史上,他们是半双工的(即数据只能够在一个方向上流动). 现在某些系统也提供全双工管道,但是为了最佳的移植性,我们决不应该预先假定系统使用此特性 他们只能够在具有公共祖先的进程间使 ...
- Python学习基本
刚开始学习是看了这个网站: 廖雪峰的官方网站 http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac92707 ...
- AngularJs的UI组件ui-Bootstrap分享(十四)——Carousel
Carousel指令是用于图片轮播的控件,引入ngTouch模块后可以在移动端使用滑动的方式使用轮播控件. <!DOCTYPE html> <html ng-app="ui ...
- Djanog结合jquery实现ajax
最近想在使用django的基础上通过jquery实现页面局部刷新的功能,研究了两天,终于是解决了这个问题,下面把方法步骤记录下来,以备以后重用. 在项目中通过两种形式实现了ajax: 第一种方法:we ...
- 清除mysql表中数据
delete from 表名; truncate table 表名; 不带where参数的delete语句可以删除mysql表中所有内容,使用truncate table也可以清空mysql表中所有内 ...
- java数据结构_笔记(5)_图的算法
图的算法 1 图的遍历图的遍历就是从图中某个顶点出发,按某种方法对图中所有顶点访问且仅访问一次.遍历算法是求解图的连通性问题.拓扑排序和求关键路径等算法的基础. 2 深度优先遍历从图中某个顶点V 出发 ...
- jsp九大内置对象和四大作用域
看到网上一些Jsp内置对象介绍的都不是很全,今天我把各位大神的整理了一下. JSP九大对象:内置对象(又叫隐含对象,有9个内置对象):不需要预先声明就可以在脚本代码和表达式中随意使用. 一.reque ...
- HTML 30分钟入门教程
作者:deerchao 转载请注明来源 本文目标 30分钟内让你明白HTML是什么,并对它有一些基本的了解.一旦入门后,你可以从网上找到更多更详细的资料来继续学习. 什么是HTML HTML是英文Hy ...