2015年7月10日星期五

数据结构与算法之链表 - 山中水寒

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
数据结构与算法之链表 - 山中水寒  阅读原文»

  软件设计中,最常用的两种数据存储结构就是顺序存储结构和链式存储结构,顺序存储结构中用的最多的便是数组了,而链式存储结构用的比较多的应该是单链表以及它们的变形。

  单链表中只有一个指向下一个结点的指针,并且最后一个元素的next指针为NULL;循环链表与单链表的区别就是最后一个指针指向头结点;双向链表中,每个结点不仅有指向下一个结点的next指针,还有一个指向前一个结点的prior指针,头结点的prior和尾结点的next为NULL。因为都是单链表的变形,所以这里主要分析单链表。

  与顺序存储空间不同,链式存储空间并不是连续的,数组中,直接通过索引来访问元素。而单链表中它是通过每个结点中的next指针来找到下一个结点的。

1、 单链表的结构体

  单链表的结构体中包含两个元素,一个是数据元素,一个是指向下一个结点的地址。下列结构体中data的数据类型可以是基本数据类型,也可以是复杂数据类型。

typedef struct SingleListNode{
int data;
struct SingleListNode
*next;
}ListNode;

2、单链表的创建

如果返回值为NULL,则创建失败,否则创建成功。

ListNode* allocNode(int data){
ListNode
*pNode;
pNode
= (ListNode*)malloc(sizeof(ListNode));

if(NULL != pNode){
memset(pNode,
0,sizeof(ListNode));
pNode
->data = data;
}
return pNode;
}

3、单链表查找

如果找到对应的元素,返回结点地址,否则返回NULL。

ListNode* findDataInSingleList(const ListNode *pListNode,int data){
ListNode
*pNode;
if(NULL == pListNode)
return NULL;
if(data == pListNode->data)
return (ListNode*)pListNode;
pNode
= pListNode->next;
while(pNode){
if(data == pNode->data)
return pNode;
pNode
= pNode->next;
}
return NULL;
}

4、单链表的插入

找到链表的最后一个结点,即next为NULL的结点,然后新建一个结点插入。

bool insertNodeIntoSingleList(ListNode **pListNode,int data){
ListNode
*pNode,*ppListNode;
if(NULL == pListNode)
return false;
pNode
= createSingleListNode(data);
if(NULL == pNode)
return false;
if(NULL == *pListNode)
*pListNode = pNode;
else{
ppListNode
= *pListNode;
while(ppListNode->next)
ppListNode
= ppListNode->next;
ppListNode
->next = pNode;
}
return true;
}

5、单链表的删除

找到元素等于data的结点pNode,用指针pPre表示pNode的前一个结点,把pPre的next指向pNode的next结点,然后free pNode.

bool deleteFromSingleList(ListNode **pListNode,int data){
if(NULL == pListNode || NULL == *pListNode)
return false;
ListNode
*pNode = *pListNode->next;
if(*pListNode->data == data){
free(*pListNode);
*pListNode = pNode;
return true;
}
ListNode
*pPre = *pListNode;
while(pNode){
if(pNode->data == data){
pPre
= pNode->next;
free(pNode);
return true;
}
pPre
= pNode;
pNode
= pNode->next;
}
return false;
}

6、释放链表

链表是通过mallic动态创建的,所以每次用完以后,必须free,以免内存泄漏。

void freeSingleList(ListNode *pListNode){
if(NULL == pListNode)
return ;
ListNode
*pNode = NULL;
while(pListNode){
pNode
= pListNode->next;
free(pListNode);
pListNode
= pNode;
}
}

关于链表的算法:

最常用的就是快慢指针,第7、8、9用的都是这个思想。

还有就是借助第三指针的概念。其实对链表的操作都是对指针的操作,理解链表首先得理解指针。下面举几个常用的链表算法。

7、找出链表的中间结点

用两个快慢指针,快指针每次走两步,慢指针每次走一步,当快指针走到链表尾部时,慢指针所指向的结点即为中间结点。

ListNode* findMidPositionOfList(const ListNode *pListNode){
ListNode
*pQuick = pListNode,*pSlow = pListNode:
if(NULL == pListNode)
return NULL;
while(pQuick){
pSlow
= pSlow->next;
pQuick
= pQuick->next;
if(NULL == pQuick)
break;
pQuick
= pQuick->next;
}
return pSlow;
}

8、找出倒数第k个结点

快指针先走k步,然后快慢指针一起走,知道快指针走到链表 末尾,慢指针即为倒数第k个结点。

ListNode* findNodeCountBackwards(const
GNU make 总结 (五) - benxintuzi  阅读原文»

一、使用make更新静态库

静态库文件是一些.o文件的集合,在Linux中使用ar工具对它进行维护管理。一个静态库通常由多个.o文件组成,这些.o文件可独立的被作为一个规则的目标,库成员作为目标时需要按照如下格式来书写:

ARCHIVE(MEMBER)

注:这种格式只能出现在规则的目标依赖中,不能出现在命令行中。含有这种表达式的规则的命令行只能是ar命令或者其它可以对库成员进行操作的命令。如下规则用于创建库“foolib”,并将“hack.o”成员加入库中:

foolib(hack.o) : hack.o
  ar cr foolib hack
.o

如果在规则中同时需要指定库的多个成员,可以将多个成员放在括号中,如下:

foolib(hack.o kludge.o)等价于foolib(hack.o) foolib(kludge.o)

也可以使用shell通配符,例如foolib(*.o)代表库foolib中所有的.o成员。

静态库的更新

模式(%)用来更新目标A(M),其先将M拷贝到A中,如果之前A不存在,则首先创建A。例如:目标为foo.a(bar.o),执行时将完成:首先使用隐含规则创建bar.o,之后将bar.o加入到foo.a中。如此,bar.o就作为foo.a中的一个成员了。

静态库中,其所有的成员名是不包含目录描述的,也就是说对于静态库,当使用nm命令查看其成员时,能够获得的信息只是静态库所包含的成员名字而已。

当给一个静态库增加一个成员时,ar将.o文件简单地追加到静态库的末尾,并没有及时更新库的符号表。如果此时我们使用这个库进行链接生成可执行文件,链接程序ld却提示出错,可能原因是:程序使用了刚加入的.o文件中定义的函数或者全局变量,但链接程序无法找到。此时需要使用ranlib来对符号表进行更新。

静态库中存在一个特殊的成员__.SYMDEF,其记录了库中所有成员的符号(函数名、变量名)。使用方法为:

ranlib ARCHIVEFILE

通常在makefile中我们可以这样来实现:

libfoo.a : libfoo.a(x.o) libfoo.a(y.o)
  ranlib libfoo
.a

二、make隐含规则

使用make的隐含规则,在makefile中就不需要明确给出重建某一目标的命令,甚至可以不需要规则。make会自动根据已存在的源文件类型来启动相应的隐含规则。例如:

foo : foo.c bar.o
  cc –o foo foo
.o bar.o $(CFLAGS) $(LDFLAGS)

上述并没有给出重建foo.o的规则,make会根据隐含规则来创建这个文件。隐含规则所提供的只是一个最基本的对于关系:例如EXENAME.o对应EXENAME.c,EXENAME对应EXENAME.o。

每一个内嵌的隐含规则都存在一个目标模式和一个依赖模式,而且同一个目标模式可以对应多个依赖模式。通常make会对那些没有命令行的规则、双冒号规则寻找一个隐含规则来执行。

注:即使给一个目标指定了明确的依赖文件,那么隐含规则依然会被执行,如下所示:

foo.o : foo.p

如果当前目录下存在foo.c,那么执行make时并不用pc编译,而是用cc编译。

如果不想让make为一个没有命令行的规则中的目标搜索隐含规则时,可以使用空命令来实现。

1 make隐含规则

自动由.c生成.o                 $(CC) –c $(CPPFLAGS) $(CFLAGS)
自动由
.cc或.C生成.o              $(CXX) –c $(CPPFLAGS) $(CFLAGS)
自动由
.o生成可执行文件,使用链接器GNU ld $(CC) $(LDFLAGS) *.o $(LOADLIBS) $(LDLIBS)
                        仅适用于由一个源文件直接生成可执行文件的情况;
                        如果需要由多个源文件共同创建一个可执行文件,需要在makefile中添加隐含规则的依赖文件,如:x : y
.o z.o

2 隐含变量

隐含变量分为两类,一类表示命令的名字,一类表示命令使用的参数,多个参数使用空格分隔。

表示命令的名字

AR

函数库打包程序,默认是ar,用于创建静态库.a

AS

汇编程序,默认是as

CC

C编译器,默认是cc

CXX

C++编译器,默认是g++

CO

从RCS中提取文件的程序,默认是co

CPP

C程序的预处理器,默认是$(CC) -E

GET

从SCCS中提取文件,默认是get

LEX

将Lex语言转变为C或Ratfo程序,默认是lex

YACC

Yacc文法分析器,默认是yacc

MAKEINFO

转换Texinfo源文件.text到Info文件,默认是makeinfo

TEX

从Tex源文件创建Tex Dvi,默认是tex

TEXI2DVI

从Tex源文件创建Tex Dvi,默认是texi2dvi

WEAVE

转换Web到Tex,默认是weave

没有评论:

发表评论