引言:
数据结构中,很重要的一个结构就是链表。链表虽然简单,但却能很好的考查程序员的基本功,因此在很多面试、笔试中会直接或间接的考查它。下面就对链表进行系统的复习。
分析描述:
下面就对链表的构建、删除、插入、查找、求长等操作进行系统描述。并给出程序代码。其中结点的定义如下:
typedef int ElementType;
typedef struct ListNode{
ElementType data;
struct ListNode *NextNode;
}ListNode, *PListNode;
◆◆◆◆◆链表的初始化。一般认为链表是由头结点开始,后面跟着的各个结点。因此,创建链表之前,可以先创建一个头结点。头结点的结构可以如图表示,
PListNode InitList(void)
{
PListNode L;
L = (PListNode)malloc(sizeof(ListNode));
if(L == NULL){
fprintf(stderr, "init list fail.\n");
exit(EXIT_FAILURE);
}
L->NextNode = NULL;
return L;
}
此处,只是创建了一个头结点L,并让其NextNode指向了空指针NULL。并未对它的data进行赋值。该程序要注意的一点是L->NextNode = NULL语句。
◆◆◆◆◆链表的创建。链表的创建就是从链表的头结点开始,往链表中添加结点元素。如图所示:
PListNode CreateList(PListNode L)
{
PListNode TmpNode = (PListNode)malloc(sizeof(ListNode));
if(TmpNode == NULL){
fprintf(stderr, "malloc list node fail.\n");
return L;
}
while(scanf("%d", &TmpNode->data) != 0){
TmpNode->NextNode = L->NextNode;
L->NextNode = TmpNode;
TmpNode = (PListNode)malloc(sizeof(ListNode));
if(TmpNode == NULL){
fprintf(stderr, "malloc list node fail.\n");
return L;
}
}
free(TmpNode);
return L;
}
注意,上面的一段代码有些技巧,比如while(scanf("%d", &TmpNode->data) != 0)就是利用了scanf函数的返回值,另外,在while之前先分配结点空间,最后while之后用free释放多分配的一个空间(因为scanf函数如果失败,就用不到最后分配的空间内存了,为了规范代码,应该用free释放掉这块内存)。
◆◆◆◆◆链表的遍历。链表的遍历就是指对从链表的头结点开始,依次输出链表中各个结点的值。
PListNode TraverseList(const PListNode L)
{
PListNode TmpNode = L;
if(TmpNode->NextNode == NULL){
printf("ListNode is empty.\n");
return;
}
while(TmpNode->NextNode != NULL){
printf("%d ", TmpNode->NextNode->data);
TmpNode = TmpNode->NextNode;
}
putchar('\n');
return L;
}
注意:这段代码容易有一个极易出错的地方:因为传给函数的参数是链表的指针,所以在函数内部应该先用PListNode TmpNode = L语句将指针赋值给临时结点指针,否则如果直接用L->NextNode = L->NextNode->NextNode会使得链表的结构破坏。
◆◆◆◆◆链表的求长。求出所给链表的长度,即链表中除头结点之外的其他结点的个数。
int ListLength(PListNode L)
{
int count = 0;
PListNode Tmp = L;
while(Tmp->NextNode != NULL){
Tmp = Tmp->NextNode;
count++;
}
return count;
}
该段代码同样应该注意PListNode Tmp = L语句,以免破坏链表。
◆◆◆◆◆判断链表是否为空。如果链表为空,即链表只包含头结点,其NextNode指针应该为NULL,因此程序很简单。
int IsEmpty(PListNode L)
{
return (L->NextNode == NULL);
}
◆◆◆◆◆链表中查找某元素的位置。在给定的链表中查找某个给定值是否位于链表中,如果在链表中,就给出具体的位置。如果不存在链表中,就返回0,以表示未找到该元素。
int FindPosition(ElementType data, PListNode L)
{
int Index = 0;
PListNode TmpNode = L;
while(TmpNode->NextNode != NULL){
Index++;
if(TmpNode->NextNode->data == data)
return Index;
TmpNode = TmpNode->NextNode;
}
return 0;
}
◆◆◆◆◆删除链表中的元素。删除链表中某个存在的结点,如果结点不存在,则不做任何事。
PListNode Delete(ElementType data, PListNode L)
{
PListNode TmpNode = L;
if(TmpNode->NextNode == NULL){
printf("the list you give is an empty list.\n");
return L;
}
while((TmpNode->NextNode != NULL) && (TmpNode->NextNode->data != data)){
TmpNode = TmpNode->NextNode;
}
if(TmpNode->NextNode == NULL){
printf("there is no elementtype %d.\n", data);
return L;
}
TmpNode->NextNode = TmpNode->NextNode->NextNode;
return L;
}
◆◆◆◆◆链表的插入。给定一个结点的值,将它插入到链表中的某个位置处。
PListNode Insert(ElementType data, PListNode L, int Index)
{
int index = 0;
PListNode TmpList = L;
if(L == NULL || Index < 0 || Index > ListLength(L)){
printf("the input element wrong.\n");
return;
}
PListNode TmpNode = (PListNode)malloc(sizeof(ListNode));
if(TmpNode == NULL){
printf("malloc memory fail.\n");
return;
}
TmpNode->data = data;
for(index = 0; index < Index; index++){
TmpList = TmpList->NextNode;
}
TmpNode->NextNode = TmpList->NextNode;
TmpList->NextNode = TmpNode;
return L;
}
注意,对于要插入的操作,一定要明白是插入到某个位置的前面还是插入到某个位置的后面。
◆◆◆◆◆链表的删除。删除链表就是将链表中的结点全部释放掉,即释放掉除头结点以外所有结点的内存空间。
PListNode DeleteList(PListNode L)
{
PListNode Tmp1, Tmp2;
Tmp1 = L->NextNode;
while(Tmp1){
Tmp2 = Tmp1->NextNode;
free(Tmp1);
Tmp1 = Tmp2;
}
L->NextNode = NULL;
return L;
}
总结:
1、对于链表中的某些操作,应该特别注意对内存指针的使用。一定要清楚应该何时使用malloc来获取一个新的单元。要记住,声明指向一个结构的指针并不创建该结构,而只是给出足够的空间容纳结构可能会使用的地址。
2、由于在给链表相关的函数传递参数时,经常传递的是指针,这样很有可能导致指针所指向的位置发生改变,一个好的解决办法就是在函数内,创建临时的指针变量,使其与指针参数相等。指针参数作为返回值返回给调用函数,这样就能保证指针参数不会因为函数内部的某些动作而修改。
3、链表的这些操作是非常基础的,在面试笔试题中经常会直接或间接的考查到,因此一定要牢牢掌握。