目录
一 概念
可以用一组任意的存储单元存储线性表的数据元素(a1
a2
a3
… an
)
单链表(Single Linked List): n个结点链接成一个链表,即为线性表的链式存储结构,每个结点中只包含一个指针域
结点(Node) : 数据元素的存储映像。它包含两个域:数据域和指针域。
数据域(Data Domain): 结点中存储数据元素信息的域。
指针域(Pointer Domain): 结点中存储下一个存储位置的域 指针(pointer):指针域中存储的信息。
头指针(Header Pointer):链表中第一个结点的存储位置 ,如果有头结点,则存储头结点的地址。 单链表一般就是以头指针的名字来命令。如LinkList * list。
头结点(Header Node):单链表中的第一个结点前的预设的结点。数据域内不存储任何信息,指针域存储单链表中第一个结点的位置。
二 数据对象的类型
结点类型 LNode
typedef struct Node
{
ElemType data ; /*节点数据, */
struct LNode * next; /*指向下一个节点的指针 */
} LNode; /*定义节点类型,并定义一个指向节点的指针 LinkList,作为线性表的开始*/
头指针类型 LinkList
typedef struct Node *LinkList; // 用Linklist 定义的变量,就是头指针,也就是链表的名称
宏定义
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
三 基本操作
3.1 初始化链表 InitList
原型: Status InitList(LinkList * list)
说明: 初始化链表,为头结点申请地址,如果申请成功,将头结点地址赋给头指针 list。
返回: 初始化成功,返回 1;失败,返回 0。
Status InitList(LinkList * list)
{
if(*list=(LNode*)malloc(sizeof(LNode)==NULL)) //申请一个Lnode大小的内存作为 头结点,并将头结点的地址给list,一个空链表list自 此 就形成
return ERROR;
(*list)->next=NULL; /*没有数据节点,所以将next 设置为NULL */
return OK;
}
3.2 销毁链表 DestoryList
原型 :void DestoryList(LinkList * list)
说明:毁链表list,将列表中包括头结点的所有结点的内存全部释放
/*销毁链表list,包括头结点的地址也释放*/
void DestoryList(LinkList * list)
{
LNode * nodePtr, *tempNodePtr; //定义两个指针,指针nodePtr用来作为循环的游标,指针tempNodePtr用来将nodePtr传递过来的地址 释放内存
nodePtr=*list; /*从头结点开始释放*/
while(nodePtr !=NULL)
{
tempNodePtr = nodePtr; /* 先将地址存放到临时指针中 */
nodePtr = nodePtr->next; /* 指向下一个*/
free(tempNodePtr); /* 释放地址 */
}
*list=NULL; /* 地址都释放完了,最后list置NULL,防止野指针 */
}
3.3 插入结点 ListInsert
原型 : Status ListInsert(LinkList list,int i,ElemType elem)
说明 : 在链表list中的第i个位置插入结点,结点的数据为elem
返回: 插入成功,返回1;插入失败,返回 0
/*在list的第i个位置插入元素elem*/
Status ListInsert(LinkList list,int i,ElemType elem)
{
int j=1;
//定义两个指针,指针nodePtr用来作为循环的游标,指针newNodePtr用来保存元素elem;
LNode * nodePtr,*newNodePtr;
nodePtr =list; /*定义一个Node指针,初始值指向头结点*/
while( j<i && nodePtr != NULL ) /*寻找第i个结点,当j小于i(还没有到i的位置), 当nodePtr不为空且 */
{
nodePtr = nodePtr->next;
++j; //++j 和上面一行代码 在一个循环中,说明 j每增加1,nodePtr指向就往后移一个位置。
}
if(nodePtr==NULL) /*到结尾了也没有找到第i个位置 */
return ERROR;
newNodePtr=(LNode *) malloc(sizeof(LNode)); /*为结点申请内存*/
if(newNodePtr==NULL)
return ERROR;
newNodePtr->data=elem;
newNodePtr->next=nodePtr->next;
nodePtr->next=newNodePtr;
return OK;
}
3.4 删除结点 ListDelete
原型 : Status ListDelete(LinkList list,int i,ElemType * elemPtr)
说明:在list中找到第i个结点,将结点中数据存放到elemPtr中,删除该结点
返回: 删除成功,返回1;删除失败,返回 0
/*在list中删除第i个元素,并将第i个元素存放到elemPtr指针中*/
Status ListDelete(LinkList list,int i,ElemType * elemPtr)
{
int j=1;
LNode * nodePtr, *nextNodePtr;
nodePtr=list; //这里之所以用notePtr=list头结点的指针,而不是notePtr=list->next第一个节点指针
//如果使用后者,找到第i个元素,正好是notePtr指向的位置,那么找到位置的前驱元素就没有变量指向了,就没法修改前驱的next了
while (j<i && nodePtr->next!= NULL) // 当j<i时说明没有找到
{
nodePtr = nodePtr->next;
++j;
}
if(nodePtr->next == NULL) /*说明已经是最后一个节点了*/
return ERROR;
nextNodePtr=nodePtr->next;
*elemPtr=nextNodePtr->data;
nodePtr->next=nextNodePtr->next;
free(nextNodePtr);
return OK;
}
3.5 访问元素 Visit
原型 : Status Visit(ElemType elem)
说明: 对elem操作,具体什么操作可以根据需要修改,本示例中是打印elem的值
/*对elem操作,具体什么操作可以根据需要修改,本示例中是打印elem的值*/
Status Visit(ElemType elem){
printf("%d\t",elem);
}
3.6 遍历链表 ListTraverse
原型 :Status ListTraverse(LinkList list, Status(*Visit) (ElemType elem) )
说明 :遍历链表list,第二个参数是一个函数指针,这样可以更换其他函数,本例中为遍历打印。
/*遍历线性表,并对每一个元素调用Visit函数,一旦Visit()失败,这遍历失败 */
Status ListTraverse(LinkList list, Status(*Visit) (ElemType elem) ){
LNode * nodePtr=list->next;
while(nodePtr!=NULL)
{
(*Visit)(nodePtr->data);
nodePtr=nodePtr->next;
}
printf("\n");
return OK;
}
结束语 单链表的优缺点
优点:
- 根据需要分配内存空间,不浪费内存空间
- 插入和删除速度快,时间复杂度为O(1)
缺点:
查找慢,时间复杂度为O(1)