链表
链表是一个结构体。
所以实现一个链表需要链表结构体以及节点结构体
节点结构体
typedef struct node{//节点的结构体 int a; struct node* next; //struct node* previous; }node,*pnode;
在此例中,a为节点所附带的属性,不一定只有一个int型的变量,还可以有char name[15]等其他数据。
为方便起见,用typedef关键字为节点结构体重新命名
链表结构体大体类似
1 typedef struct {//链表的结构体 2 pnode head; 3 pnode last; 4 int lenth; 5 }list,*plist;
在这个节点中,可以加入链表的额外的属性,如链表的长度等
有些书中,不定义链表结构体,仅仅只用一个节点类型的指针全局变量head来代替,这也是可行的。但如果我们要创建多个链表,这样子做就不太好了。而且,一个链表可以有多个属性,如果写成结构体类型,对于属性的表达显然更有益处。
对于链表,他的特点便是其长度不定。这是使用链表而不使用数组的主要原因之一。而实现其长度任意增长的函数便是malloc函数,而malloc函数的原型是:
void *malloc(long NumBytes);
首先,malloc函数的功能是向系统申请一块内存。其次我们可以看到,malloc函数的参数只有一个,long NumByte。这个long型参数代表的是向系统申请的内存的大小。因为不同的系统为各类型变量分配内存的大小不同,所以我们一般用sizeof来计算其大小。 malloc返回的是一个空指针,所以我们要对其进行强制转换。还有就是对于使用malloc函数分配的内存系统不会自动释放,所以在程序结尾我们要对它进行内存释放,用free函数。free函数的原型为:void free(void *FirstByte); 其参数为所欲释放内存的地址,也就是指向这个内存的指针。
对于链表,实质上是对指针的应用。节点与节点间的联系通过 节点里的定义的 节点结构体的指针变量 来表示,在这一个例子里,就是next变量。
对于单向链表,节点里只有一个指针变量,这个指针变量存储着下一个节点的地址,通过对这个指针变量的解引用,可以获得下一个节点的内容。 我们可以把指针变量想象成一条铁链,连接着两个节点,那么多个节点互相连接,就形成了一个链条,这就是链表了。而对于这条铁链,我们只能通过首尾来访问、使用这个链条,所以我们把他包装起来,那么,这个包装链条的包装袋要把这条铁链的头和尾露出来,这就是head和last指针。 这个包装袋上还要写明这个铁链的长度,就是length变量。
对于双向链表,节点里有两个指针变量,在本例中就是被注释掉的previous变量,其原理就是在previous变量里存储上一个节点的指针,那么,我们不仅能从head遍历链表到last,还能通过last遍历到head,简而言之,这个链表是双向的。
链表的基本操作为增删改查。
首先为增
void add(plist p){ int i=0,j=0,k=0; pnode news=(pnode)malloc(sizeof(node));//用malloc开辟的内存 最后要记得释放 scanf("%d",&news->a); news->next=NULL;//对news进行赋值 if(0== p->lenth){//此时,链表为空 p->head=news;//链表的头指针指向新节点news的地址 p->last; //此时 链表为空 所以新节点news也是最后一个节点 p->lenth=p->lenth+1; // news已经加入链表,链表长度加一 } else{// 如果链表长度不为空 p->last->next=news;//链表的尾节点指向新节点news p->head=news; //链表的尾指针向后移,指向新加入的节点news,此时news为最后一个节点 p->lenth++; //链表长度加一 }
//p->lenth++;
}
在此例里,我们先通过malloc函数向系统申请一块内存来存放新的节点,在对节点进行初始化。在本例中,对节点的初始化的语句只有:scanf("%d",&news->a); 但在实际应用中,因为节点的数据域会有多个数据,所以其初始化语句会更多,而多个初始化语句中就要注意缓冲区的刷新了。 在这边我举个例子: scanf("%d",&a); gets(c); 在这里,输入完成后,c的值为回车符。这是因为在输入完scanf后,我们敲击回车,产生回车符,这个回车符留在了缓冲区,被gets函数读取了。
要解决这个问题,一个方法就是这样写:scanf("%d",&a); getchar(); gets(c); 吧回车符用getchar吃掉。
还有一个方法就是用flush()函数刷新缓冲区。这样写的逼格会比较高,但是高不到哪里去。
还有一种方法就是这样写:scanf("%d%*c",&a); gets(c); 这里%*c在scanf里的作用就是读取一个字符并丢弃它。scanf函数中 比如 scanf(" %c",&c); 这在%号前加了空格,其作用为跳过所有空白字符,读取第一个非空白字符,赋值给变量c
推荐熟记个个输入函数的效果,比如scanf函数读取%s时遇到空格就停止读入,gets函数在读取一串字符串后读取并丢弃回车,puts函数会自动添加一个回车。这样才能写出更灵活的代码。
在初始化完成后,我们就要判断这个链表的长度是否为空,若链表为空表,则在新节点为第一个节点,也是最后一个节点。我们head和last都要指向它。如果链表并不是空表,我们就把这个节点加入的链表的后面。所谓加入链表,就是令原链表的最后一个节点的next指针指向这个新加入的节点,在令链表的last指针指向新节点,则新节点就成为新的最后一个节点。有些帖子上说的什么头插法,尾插法,大概的原理是一样的。
在完成上述操作后,将链表的长度加一。
然后就是删。
1 void deletes(plist p,int n)//删除链表索引n处的节点 2 { 3 int i=0,j=0,k=0; 4 pnode iterator; 5 iterator=p->head; 6 for(i=1;i<n;i++){//遍历链表至所要删除节点的前一个节点处 7 iterator=iterator->next; 8 } 9 iterator->next=iterator->next->next; 10 free(iterator->next); 11 p->lenth--; 12 }
删的原理就是将要删除节点的上一个节点的next指针指向要删除的节点的下一个节点。这样就把要删除的节点从链表中删除了。 因为在增加函数中我们用malloc函数申请的内存,所以在删除中我们要用free释放要删除节点的内存,最后不要忘记将链表的长度减一。
在这个代码中我们用了一个iterator变量来存储中间节点,以此来迭代遍历整个链表。
在然后就是查
void search(plist p,int n){//查询索引n处的节点 int i=0,j=0,k=0; pnode iterator; iterator=p->head; if(0==p->lenth){ printf("错误,链表的长度为零"); return ; } for(i=1;i<n;i++){ iterator=iterator->next; } printf("索引n处的节点的值为:%d",iterator->a); } void search2(plist p ,int n){//查询值为n的节点的索引值 int i=0,j=0,k=0; pnode iterator; iterator=p->head; for(i=0;i<p->lenth;i++){ if(n==iterator->a) printf("第%d个节点为所求节点之一",i+1); iterator=iterator->next; } }
查的操作并无什么难的,便不多讲了。
至于该,与上述函数大同小异,就不写了
最后也是重中之重的便是销毁整个链表
void destroy(plist p){ int i=0; pnode iterator=p->head;//迭代器iterator指向链表的第一个节点 if(0==p->lenth) return ;//如果链表为空,这返回,不进行任何操作 for(i=0;i<p->lenth;i++){ p->head=p->head->next;//将head指向第二个节点 free(iterator); iterator= p->head; } }
遍历整个链表,将其节点一个一个的释放掉内存。 然后再main函数的末尾调用这个函数。
这就是链表了。