使用指针实现的线性表——链表

来源:互联网 发布:中国国家网络安全局 编辑:程序博客网 时间:2024/06/04 01:23

前一小节介绍使用数组实现了线性表,这一小节使用指针来实现:

先看那12个函数:

#include <stdio.h>#include <malloc.h>typedef int ElemType;typedef struct LNode{//存放的数据ElemType data;//指向下个节点的指针LNode *next;}LNode,*LinkList;//初始化链表bool initList(LinkList *lst){*lst = (LinkList)malloc(sizeof(LNode));if(NULL == lst)return false;(*lst)->next = NULL;return true;}//删除链表void deleteList(LinkList *lst){LinkList p = (*lst)->next;LinkList q;while(p){q = p->next;free(p);p = q;}free(*lst);*lst = NULL;}//清空链表void clearList(LinkList *lst){LinkList p = (*lst)->next;LinkList q;while(p){q = p->next;free(p);p = q;}(*lst)->next = NULL;}//判断链表是否为空bool is_empty(LinkList *lst){if(NULL == (*lst)->next){printf("the list is empty! \n");return true;}else{printf("the list is not empty! \n");return false;}}//遍历链表并打印void printList(LinkList *lst){printf("list elements are: ");LinkList p = (*lst)->next;while(p){printf("%d",p->data);p = p->next;}printf("\n");}//计算链表中的元素个数int listLength(LinkList *lst){int cnt = 0;LinkList p = (*lst)->next;while(p){++cnt;p = p->next;}return cnt;}//在指定位置插入元素bool insertElem(LinkList *lst,int index,ElemType *e){int cnt = 0;LinkList p = (*lst);LinkList q = (LinkList)malloc(sizeof(LNode));while(p){if(index == cnt){//新插入节点的指针指向当前节点的指针指向的元素q->next = p->next;//当前节点的指针指向新插入的节点p->next = q;//存入数据q->data = *e;return true;}++cnt;p = p->next;}return false;}//删除某个位置的元素bool deleteElem(LinkList *lst,int index,ElemType *e){int cnt = 0;LinkList p = (*lst)->next;//存储当前节点的前一个节点LinkList q = *lst;while(p){if(index == cnt){//获取即将删除的节点的元素*e = p->data;//当前节点的前一个节点指向当前节点的后一个节点q->next = p->next;//释放当前节点free(p);break;}p = p->next;q = q->next;++cnt;}return false;}//获取某个元素的位置int locateElem(LinkList *lst,ElemType e){int index = 0;LinkList p = (*lst)->next;while(p){if(e == p->data)return index;++index;p = p->next;}return -1;}//获取某个位置的元素bool getElem(LinkList *lst,int index,ElemType *e){int cnt = 0;LinkList p = (*lst)->next;while(p){if(index == cnt){*e = p->data;return true;}++cnt;p = p->next;}return false;}//获取下一个元素bool nextElem(LinkList *lst,ElemType curr,ElemType *nxt){LinkList p = (*lst)->next;while(p){if(curr == p->data){*nxt = p->next->data;return true;}p = p->next;}return false;}//获取前一个元素bool priorElem(LinkList *lst,ElemType curr,ElemType *pre){LinkList p = (*lst)->next;LinkList q = (*lst);while(p){if(curr == p->data){*pre = q->data;return true;}q = q->next;p = p->next;}return false;}void inputList(LinkList *lst,int n,int* arr){for(int i = n-1; i >= 0; --i){LinkList pnew = (LinkList)malloc(sizeof(LNode));pnew->data = arr[i];pnew->next = (*lst)->next;(*lst)->next = pnew;}}


 

其中最后一个并不是必须的,只为了输入的时候方便。

对指针不太熟悉的朋友可能有点不明白参数是怎么传递的,这里通过一个具体的例子来说明一下。

void reset(int *x){*x = 0;}


 

在主函数中:

int a = 1;printf("before reset: %d\n",a);reset(&a);printf("after reset: %d\n",a);


这个程序相信大家都见过,但是我还是要仔细地说明一下编译器是怎么对待函数调用的:它会将实参复制一份给形参,然后对形参进行操作。所以,形参的改变是不会影响实参的,如果想改变主调函数中某个实参的值,必须将它的地址传递给被调函数—虽然这里还是会发生实参复制给形参的情况,但是由于复制的是地址,所以被掉函数中,只要对地址进行解引,就能操作主调主调函数中的值了。
再来看我们的程序:

typedef struct LNode{ElemType data;LNode *next;}LNode,*LinkList;


定义了指向结构体的指针,当我们的操作并不需要修改指针本身时,我们大可以传递指针本身:

//判断链表是否为空bool is_empty(LinkList lst){if(NULL == (lst)->next){printf("the list is empty! \n");return true;}else{printf("the list is not empty! \n");return false;}}


但是如果我们的操作需要修改这个指针时,那么我们只能给函数传递这个指针的地址,然后在函数中通过解引操作“*”来或得指针本身,然后在进行内存分配、插入、删除等操作。所以我们的很多函数的型参都有LinkList *lst。而在主函数中,将这个指针的地址传递给函数,比如initList(&myList);
与“值”传递相比,传递指针看上去很麻烦,但是它有一个明显的效率优势:当直接传递值时,会复制一个值,如果复制的是一个复杂的结构体,那么代价是非常大的;但如果复制的仅仅是一个指针,那么只需要4个字节(因为我们的电脑是32位的,所以地址也是32位,就是4个字节)。
相比之下,如果使用C++中的“引用”概念就要方便的多:直接将实参与形参关联起来,通过形参的改变,就能改变实参,而且并没有任何“复制”发生。

与数组实现的线性表相比,链表的优势在于可以方便的插入、删除元素;但是对于随机访问某个元素,却非常缓慢,必须要通过从头开始遍历整个链表开始,所以程序中总是出现

LinkList p = l->next;while(p){//具体操作p = p->next;}


 

的结构。总体上来说,二者各有利弊,如果的数据需要频繁的插入、删除,那么选择链表;如果需要频繁的随机范文,那么选择顺序表。

其实链表还有另外几种复杂的结构,比如:循环链表、双向链表、双向循环链表等等,由于基本的原理是相同的,这里只对他们进行简要的介绍。

循环链表很简单,就是最后一个节点的指针next重新指向链表头。它的好处在于不论你现在处于链表的什么位置,总能遍历到链表的所有元素。程序部分的改动也比较小,主要是在初始化时,将next指向链表头,在遍历链表的过程中,循环的停止条件变为:while(p != (*lst))。

双向链表是在基本链表的基础上,增加一个指向前一个节点的指针prior,在插入、删除操作时,指针的指向需要仔细:

 

#include <stdio.h>#include <malloc.h>#define ElemType inttypedef struct DNode{ElemType data;DNode *next;DNode *prior;}DNode,*DLinkList;bool initDList(DLinkList* dlst){*dlst = (DLinkList)malloc(sizeof(DNode));if(*dlst == NULL)return false;(*dlst)->next = NULL;(*dlst)->prior = NULL;return true;}void deleteDList(DLinkList* dlst){DLinkList p = (*dlst)->next;DLinkList q;while(p){q = p->next;free(p);p = q;}free(*dlst);*dlst = NULL;}bool insertElem(DLinkList* dlst,int index,ElemType e){int cnt = 0;DLinkList p = *dlst;while(p){if(index == cnt){DLinkList pnew = (DLinkList)malloc(sizeof(DNode));pnew->data = e;pnew->next = p->next;pnew->prior = p;p->next = pnew;//注意:当第一次插入元素或者在最后一个位置插入元素时,p->next是空的,并没有节点if(pnew->next!=NULL)pnew->next->prior = pnew;return true;}p = p->next;++cnt;}return false;}void deleteElem(DLinkList* dlst,int index,ElemType *e){int cnt = 0;DLinkList p = (*dlst)->next;while(p){if(index == cnt){*e = p->data;p->prior->next = p->next;//如果删除的是链表末尾的元素,则不需要这个步骤if(p->next != NULL)p->next->prior = p->prior;free(p);break;}++cnt;p = p->next;}}void printList(DLinkList* dlst){printf("elements are: ");DLinkList p = (*dlst)->next;while(p){printf("%d\t",p->data);p = p->next;}printf("\n");}


这里给出了比较详细的代码。尤其需要注意,当你第一次向链表中插入元素,或者插入元素的位置在链表的末尾,或者删除链表的最后一个元素时,此时p->是为NULL的,所以不能取p->next->prior 。
原创粉丝点击