二、线性表的两种存储结构
2.链式存储结构
定义:为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储本身的信息之外,还需存储一个指示其直接后继的信息。我们将存储数据元素信息的域成为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,称为结点Node。
n个结点链接成一个链表,即为线性表(a1,a2,a3,…,an)的链式存储结构,因为此链表的每个结点只包含一个指针域,所以又叫单链表。
对于线性表来说,第一个结点的存储位置叫做头指针,最后一个结点指针为空(通常用NULL或^符号表示)。
下面给出线性表链式存储结构代码描述:
Node结构体的定义如下:
#ifndef _LINK_LIST_H
#define _LINK_LIST_H
#define TRUE 0
#define FALSE -1
typedef int ElemType;
typedef int BOOL;
typedef struct Node
{
ElemType data;
struct Node * next;
}Node;
typedef struct Node * LinkList;
/*基于上述链表结构体的操作的声明:*/
/*ADT 中的实现*/;
extern BOOL ListInit(LinkList *L);
extern BOOL ListEmpty(LinkList L);
extern void ClearList(LinkList *L);
extern BOOL GetElem(LinkList *L, int i, ElemType * e);
extern int LocateElem(LinkList *L,ElemType e);
extern BOOL ListInsert(LinkList *L, int i, ElemType e);
extern BOOL ListDelete(LinkList *L, int i, ElemType *e);
extern int ListLength(LinkList L);
/*ADT上的操作的扩展*/
#endif
LinkList.h
单链表的操作的实现如下:
/**
* File name: LinkList.c
* Author:MusaGeek Version: 1.0 Date: 2018-11-21
* Description: 单链表的操作的实现的.c文件
* Function List: // 主要函数列表,每条记录应包括函数名及功能简要说明
BOOL ListInit(LinkList *L); 初始化链表
BOOL ListEmpty(LinkList L); 判断链表是否为空
void ClearList(LinkList *L); 清空链表
BOOL GetElem(LinkList *L, int i, ElemType * e); 获取链表指定位置的元素
int LocateElem(LinkList *L,ElemType e); 定位元素的位置
BOOL ListInsert(LinkList *L, int i, ElemType e);插入元素
BOOL ListDelete(LinkList *L, int i, ElemType *e);删除元素
int ListLength(LinkList L); 获取链表的长度
*/
#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h"
/*************************************************
Function: ListInit
Description: 初始化单链表中的数据
1.为指向链表的头指针分配头结点空间
2.将头结点的指针域设置为NULL
Input:
L : 指向链表头结点的指针
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListInit(LinkList *L)
{
Node *head = (Node *)malloc(sizeof(Node));
if(!head)
return FALSE;
*L = head;
head->next = NULL;
return TRUE;
}
/*************************************************
Function: ListInit
Description: 初始化单链表中的数据
1.为指向链表的头指针分配头结点空间
2.将头结点的指针域设置为NULL
Input:
L : 指向链表头结点的指针
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListEmpty(LinkList L)
{
return ( (L->next) ? FALSE : TRUE);
}
/*************************************************
Function: ClearList
Description: 因为 LinkList 中的结点均是malloc而来的 , 因此当整个链表不使用了时候 需要手动释放掉,避免消耗内存
但是保留头结点
1.创建 指针 p = (*L)->next , 指针 q;
2.循环 :当 p 不为 NULL 时候 ; q = p->next;释放 p; p = q;
Input:
L : 指向链表头结点的指针
Return: void
*************************************************/
void ClearList(LinkList *L)
{
Node *p = (*L) = (*L)->next, *q = NULL;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; /*头结点不释放 ,指针域置为NULL*/
}
/*************************************************
Function: GetElem
Description: 获取顺序表中指定位置的元素 , 时间复杂度O(n) , 时间浪费在遍历上面:
1.声明一个结点p指向链表的第一个结点 , 初始化 j 从1开始
2.当 j < i 时 , 就遍历链表 , 让指针p不断的向后移动 , j+=1
3.若最终 NULL == p 则 返回FALSE
4.若 NULL != p 则 将data域数据传出 返回TRUE ,
Input:
L : 指向链表头结点的指针
i : 获取元素的位置
Output:
*e : 输出类型参数 , 获取 i 指向的元素
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL GetElem(LinkList *L, int i, ElemType * e)
{
Node * p = (*L)->next;
int j = 1;
while(p && j < i)
{
p = p->next;
j++;
}
*e = p->next->data;
return (p ? TRUE : FALSE);
}
/*************************************************
Function: LocateElem
Description: 定位e在链表中的位置, 时间复杂度O(n), 时间浪费在遍历上面:
遍历找到第一个匹配的元素即可
Input:
L: 指向链表头结点的指针的指针
e: 需要定位的元素值
Return: int 元素在链表中的位置,没有匹配到为-1
*************************************************/
int LocateElem(LinkList *L,ElemType e)
{
Node *p = (*L)->next;
int j = 1;
while(p && p->data != e)
{
p = p->next;
j++;
}
return p ? j : -1;
}
/*************************************************
Function: ListInsert
Description: 在指定 i 的位置插入元素 , O(n) , 浪费的时间在遍历上面 , 但是没有移动元素造成的复制开销
1.声明一个结点 p 指向链表的头结结点 , 初始化 j 从 1 开始
2.当 j < i 遍历链表 , 让p指针不断后移 , j+=1
3.若 遍历结束 , NULL == p , 返回FALSE
4.否则 当 j == i 时候 , p 已经指向的了 第i个元素之前的元素
5.创建结点s(malloc) , s->next = p->next; p->next = s;
6.返回TRUE
Input:
L : 指向链表头结点的指针
i : 获取元素的位置
e : 插入的元素值
Output:
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListInsert(LinkList *L, int i, ElemType e)
{
Node *p = *L , *s = NULL;
int j = 1;
while(p && j<i) //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
{
p = p->next;
j++;
}
if(p)
{
s = (struct Node *)malloc(sizeof(Node)); //给新插入的结点分配空间
s->data = e;
s->next = p->next; //下面两步骤不能反了
p->next = s;
return TRUE;
}
return FALSE;
}
/*************************************************
Function: ListDelete
Description: 删除指定的第 i 个位置的元素 , O(n) 但是没有移动元素复制元素的开销
1.定义指针p指向第一个结点 , 定义 j = 1;
2.在 j < i 且 p !=NULL 的情况下 让指针p向后移动 , j++
3.当 j >= i 时 若 NULL = p 那么 返回FALSE
4.否则 临时指针变量 t 保存 p->next;p->next = p->next->next; 再释放t
5.返回TRUE
Input:
L : 链表头指针
i : 获取元素的位置
Output:
*e : 输出类型参数 , 获取 i 指向的元素
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListDelete(LinkList *L, int i, ElemType *e)
{
Node *p = *L, *t = NULL;
int j = 1;
while(p && j < i) //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
{
p = p->next;
j++;
}
if(p)
{
t = p->next;
p->next = t->next; //将要删除结点剔除链表
*e = t->data;
free(t);
return TRUE;
}
return FALSE;
}
/*************************************************
Function: ListLength
Description: 获取链表的长度
直接遍历链表获得
Input:
L : 链表头指针
Return: int 链表的长度
*************************************************/
int ListLength(LinkList L)
{
Node *p = L->next;
int len = 0;
while(p)
{
len++;
p = p->next;
}
return len;
}
LinkList.c
单链表虽然在插入,删除元素上面的操作也是O(n) , 但是只是时间花费在了遍历的开销上面了,而不是元素移动上,且如果元素的类型越复杂,移动上面造成复制的开销将更大,因此单链表 比 顺序表更适合频繁的元素插入的和删除。
单链表的创建又分为 头插法 和 尾插法 两种创建方式
顺序表 和 链表的对比 :
频繁查找 , 很少进行删除 和 插入时 适用顺序表
线性表的元素变化较大 , 频繁的进行插入和删除 ,使用链表
线性表不知道元素的个数的多少的时候 , 就使用链表