0 前言
上一讲我们讲解了线性表的顺序存储,但顺序存储具有插入和删除操作时都需要进行数据元素的移动,而链式存储就不存在这样的问题。链式存储的现行表也称为链表,它使用节点来存储数据元素,节点的地址可以连续,也可以不连续。而链表又可分为单链表、双链表和循环链表,这里我们将主要介绍单链表的使用。
1 单链表的数据结构
单链表的节点由数据域和指针域两部分组成,数据域用来存储数据元素本身,指针域用来存储下一节点地址,也就是直接后继指针。每个链表都有一个头指针,通过它可以找到链表的第一个节点,然后又可以通过该节点的指针域找到它的后继节点。由于最后一个节点没有后继节点,因此它的指针域为NULL。
单链表结构示意图如下所示,其中左图为带头节点单链表,右图为不带头节点的单链表
单链表节点的定义如下所示:
struct node
{
ElemType data;
struct node *next;
};
其中,ElemType为数据元素的类型,其选取由需要而定,next为指向结构体node的指针,也就是指向链表节点的指针。
2 单链表的创建
现在我们编写代码实现一个单链表的创建,要求从键盘重复读入字符作为新节点的数据元素存储到链表中
/*
* =====================================================================================
*
* Filename: linkList.c
*
* Description:
*
* Version: 1.0
* Created: 28/08/14 09:43:48
* Revision: none
* Compiler: gcc
*
* Author: xinyi61 (xinyi61), [email protected]
* Company: cqu
*
* =====================================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
struct node
{
char data;
struct node *next;
};
typedef struct node LinkList;
LinkList *createLinkList()
{
char ch;
LinkList *p, *p1, *p2;
p = (LinkList *)malloc(sizeof(LinkList));
p->data = '\0';
p->next = NULL;
p1 = p;
printf("init linklist,please input a string:");
while((ch = getchar()) != '\n')
{
p2 = (LinkList *)malloc(sizeof(LinkList));
p2->data = ch;
p2->next = NULL;
p1->next = p2;
p1 = p2;
}
return p;
}
int main(int argc, char *argv[])
{
LinkList *p, *q;
p = createLinkList();
q = p->next;
printf("\nthe element is:");
while(q)
{
printf("%c",q->data);
q = q->next;
}
printf("\n");
return 0;
}
其效果如下:
至此,链表就创建完成了,接下来我们看一下链表的查找节点、插入节点、删除节点以及链表表长的相关分析和代码实现。
3 单链表节点的查找
链表是不支持随机节点访问的,这就使得查找数据元素时需要对节点逐个进行扫描,查找可以分为按序号查找和按值进行查找,这里将分别进行介绍
3.1 按序号查找
单链表查找的结构示意图如下
LinkList *searchByNum(LinkList *p,int n)
{
int i = 1;
LinkList *q;
q = p;
while(q != NULL)
{
if(n == i)
{
return p;
}
++i;
q = q->next;
}
return NULL;
}
该函数在指针p指向的单链表中查找第n个节点,如果存在,就返回指向该节点的指针,否则就返回NULL
3.2 按数据元素查找如下
LinkList *searchByVal(LinkList *p,char ch)
{
LinkList *q;
q = p;
while(q != NULL)
{
if(ch == q->data;)
{
return p;
}
q = q->next;
}
return NULL;
}
该函数在指针p指向的单链表中查找数据元素为ch的节点,如果存在,就返回指向该节点的指针,否则就返回NULL
4 插入节点
在单链表中插入新的节点时,首先将插入位置后继节点的指针赋给新节点的指针域,然后在将插入位置的指针域更改为新节点的指针。其操作示意图如下所示
以下是对应的代码实现
int insertNewNode(LinkList *p, int n, char ch)
{
LinkList *p1,*p2;
p1 = searchByNum(p, n-1);
if(NULL == p1)
{
printf("insert new node error\n");
return -1;
}
p2 = (LinkList *)malloc(sizeof(LinkList));
p2->data = ch;
p2->next = p1->next;//1
p1->next = p2;
return 0;
}
在这个函数中,参数p为单链表的头指针,参数n为插入的位置,参数ch为要插入的新数据元素,函数成功则返回0,否则就返回-1.
5 删除节点
当从单链表中删除节点时,只需要将将要删除的节点的直接前驱指针指向其直接后继即可,其操作如图所示
int deleteNode(LinkList *p, int n)
{
LinkList *p1,*p2;
p1 = searchByNum(p, n-1);
if(NULL == p1)
{
printf("insert new node error\n");
return -1;
}
p2 = p1->next;
p1->next = p2->next;
free(p2);
return 0;
}
在这个函数中,参数p为单链表的头指针,参数n为要删除节点的位置,函数成功则返回0,否则就返回-1.
6 链表长度的计算
链表长度计算分为带头节点的和不带头节点的,其代码实现分别如下
//dai tou jie dian de dan lian biao
int listLength(LinkList l)
{
LinkList *p = l;
int length = 0;
while(p->next)
{
p = p->next;
++length;
}
return length;
}
//bu dai tou jie dian de dan lian biao
int listLength(LinkList l)
{
LinkList *p = l;
int length = 0;
if(NULL == p)
{
return 0;
}
length = 1;
while(p->next)
{
p = p->next;
++length;
}
return length;
}
到此关于单链表的创建、查找、删除、求表长就全部讲完了。
备注:
这是微软面试开发工程师的一个常见问题:把一个字符串转换为整数。我们可以这样一个简单的实现
int strToInt(char *string)
{
int number = 0;
while(*string != 0)
{
number = number * 10 + *string - '0';
++string;
}
return number;
}
是否这个题就这样OK啦?答案显而易见是否定的。这个题目表面上看起来很简单,但是随着我们解题的深入,我们发现除了完成基本功能以外,我们还应该考虑到边界条件,错误处理等各个方面的问题。这样以来这就不是个简单的问题了。首先我们应该考虑 输入的字符串中是否有非数字字符和正负号,其次还应考虑到最大的正整数和最小的负整数以及溢出问题,最后还应该考虑当输入的字符串不能转换为整数时,我们将做怎样的错误处理。