线性表(顺序表,线性链表,循环单链表,双向链表)

一. 线性表的基本概念:

(一). 线性表的定义:

  1. 线性表是由n(n>=0)个相同类型的数据元素组成的有限序列。标记为: L=(a1,a2,...,ai,...,an)

  2. 线性表中的元素的个数n定义为线性表的长度,当n=0时为空表

  3. 当n>0时,线性表的逻辑结构如下图所示:
    在这里插入图片描述

  4. 逻辑特征:
    (1). 若至少含有一个元素,则只有唯一的一个起始元素;
    (2). 若至少含有一个元素,则只有唯一的元素;
    (3). 除了起始元素外,其他元素有且只有一个前驱元素;
    (4). 除了尾结点外,其他元素有且只有一个后继元素。
    (5). 线性表中的每个元素有唯一的序号(逻辑序号),同一个线性表中可以存在值相同的多个元素,但他们的序号是不同的。

  5. 线性表的几个概念:
    在这里插入图片描述

(二). 线性表的基本运算:

  1. 线性表L的基本运算:
·初始化InitList(L)   //建立一个空表
·销毁线性表DestroyList(L)  //释放线性表L的内存空间
·求线性表的长度ListLength(L)  //返回线性表的长度
·求线性表中第i个元素GetElem(L,i,e)  //返回线性表L的第i个元素
·按值查找LocateElem(L,x)  //若L中存在一个或多个值与x相等的元素,则其作用是返回第一个值为x的元素的逻辑序号
·插入元素ListInsert(L,i,x)  //在线性表L的第i个位置上增加一个以x为值的新元素
·删除元素ListDelete(L,i)   //删除线性表L的第i个元素aj
·输出元素值DispList(L)     //按前后次序输出线性表L的所有元素值 

对于上述的抽象数据类型线性表,还可进行一些更复杂的操作。例如,将两个或两个以上的线性表合并成一个;或将一个线性表拆分成两个或两个以上;或重新复制一个线性表等。
2. 线性表抽象数据类型List:

ADT LIST
{
	线性表中元素的逻辑结构;
	基本运算定义; 
}

二. 线性表的顺序表示和实现:

(一). 概念:

  1. 定义:线性表的顺序表示指的是用同一组地址连续的存储单元依次存储线性表的数据元素。
  2. 线性表中第 i+1 个数据元素的存储位置LOC(a i+1)和第 i 个数据元素的存储位置LOC(ai)之间的关系为:LOC(a i+1)=LOC(ai)+l
  3. 一般来说,线性表的第i个数据元素ai的存储位置为:LOC(ai)=LOC(a1)+(i-1)*l
    在这里插入图片描述
  4. 只要确定了存储线性表的起始位置,线性表中的任一数据元素都可以随机存取,所以线性表的存储数据结构说一种随机存储的数据结构。

(二). 分配内存:

  1. 线性表的动态分配顺序存储结构:
#define LIST_INIT_SIZE 100   //线性表存储空间的初始分配量
#define LIST_INCREMENT 10    //线性表存储空间的分配增量
typedef int ElemType;        //
typedef struct
{
	ElemType *elem;          //存储空间基址 
	int length;              //当前长度 
	int listsize;            //当前分配的存储容量 
}SqList;
  1. 线性表的静态分配顺序存储结构:
#define MaxSize 100
typedef int ElemType;        //假设顺序表中的所有元素为int型 
typedef struct
{
	ElemType data[MaxSize];  //存放顺序表的元素 
	int length;              //顺序表的实际长度 
}SqList;                     //顺序表类型 
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int Status;   //Status值是函数结果状态代码,如OK等 

区分: 动态分配在空间使用完毕之后可以进行扩充,而静态分配不可以。

(三). 顺序表基本运算的实现:

1**.** 初始化线性表:
主要操作: 将顺序表L的length域值为0
代码如下:

Status InitList_Sq(SqList &L)
{
	L.elem = (ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
	if(!L.elem)  return ERROR;   //存储分配失败 
	L.length = 0;               //空表长度为 
	L.listsize = LIST_INIT_SIZE;//初始存储容量
	return OK;
 } 

2. 销毁线性表:
主要操作: 由于顺序表L的内存空间是由动态分配得到的,在不需要时应该主动释放其空间。
代码如下:

Status DestroyList(SqList &L)
{
	free(L.elem);
	L.elem = NULL;
	L.length = 0;               
	L.listsize = 0;
	return OK;
}

3. 求线性表长度的运算:
主要操作: 返回顺序表L的length域值。
代码如下:

int GetLength(SqList L)
{
	return L.length;
}

4. 求线性表中第 i 个元素:
主要操作: 在位序(逻辑序号)i 无效时返回特殊值0 (假),有效时返回1(真),并用引用型形参e返回第i个元素的值
代码如下:

Status GetElem(SqList L, int , ElemType &e)
{
	if(i<1||i>L.length)
	   return ERROR;
	e=*(L.elem+i-1);
	return OK;
}   //初始条件:L已存在,且1<=i<=ListLength(L) 

5. 按值查找:
主要操作: 在顺序表L找第一个值为e的元素,找到后返回其位序(逻辑序号),否则返回0(由于线性表的逻辑序号从1开始,这里用0表示没有找到值为e的元素)。
代码如下:

int LocateElem(SqList L,ElemType e)
{
	ElemType *p;
	int i = 1;         //i的初值为第1个元素的位序 
	p = L.elem;        //p的初值为第1个元素的存储位置 
	while(i <= L.length && (*p++ != e))
	    ++i;
	if(i <= L.length)
		return i;
	else 
		return 0;
}

6. 插入算法:
主要操作:
·新元素e插入到顺序表L中逻辑序号为i的位置(如果插入成功,元素e成为线性表的第i个元素);
·i的合法值为 1<=i<=L.length+1.当i无效时返回0(表示插入失败);
· 有效时将L.elem[i-1…L.length-1]后移一个位置,在L.elem[i-1]处插入e,顺序表长度增1,并返回1(表示插入成功)。
代码如下:

Status ListInsert_Sq(SqList &L, int i,ELemType e)
{
	ELemType *p;
	if(i<1||i>L.length+1)   //i值不合法 
		return ERROR;
	if(L.length>=L.listsize) //当前存储空间已满,增加容量 
	{
		ElemType *newbase =(ElemType *)realloc(L.elem,(L.listsize + LISTINCREMENT) * sizeof(ElemType));
		if(!newbase)
			return ERROR;  //存储分配失败 
		L.elem = newbase;  //新基址 
		L.listsize += LISTINCREMENT;  //增加存储容量 
	}
	ElemType *q = &(L.elem[i-1]);     //q为插入位置 
	for(p = &(L.elem[L.length-1]), p >= q; --p)
		*(p+1) = *p;       //插入元素及之后的元素右移 
	*q = e;                //插入e 
	++L.length;            //表长增1 
	return OK;
} 

7. 删除算法:
主要操作:
·删除顺序表L中逻辑序号为i元素;
·i的合法值为 1<=i<=L.length .当i无效时返回0(表示删除失败);
· 有效时将L.elem[i…L.length-1]前移一个位置,顺序表长度减1,并返回1(表示删除成功)。
代码如下:

Status ListDelete_Sq(SqList &L, int i,ELemType &e)
{
	ELemType *p,*q;
	if(i<1||i>L.length+1)   //i值不合法 
		return ERROR;
	p = &(L.elem[i-1]);             //p为被删除元素的位置 
	e = *p;                         //被删除元素的值赋给e 
	q = L.elem + L.length - 1 ;    //表尾元素的位置 
	for(++p; p <= q; ++p)
		*(p-1) = *p;       //被删除元素之后的元素左移 
	--L.length;            //表长减1 
	return OK;
} 

三. 线性表的链式表示和实现:

(一). 线性链表:

Ⅰ. 概念:

  1. 线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。
  2. 因此,为了表示ai与ai+1的逻辑关系,对于ai来说,除了储存其本身的信息外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点。下图为 结点 的示意图:
    在这里插入图片描述
  3. 其中,直接存储数据元素信息的域称为数据域,存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针
  4. 线性结构:
    在这里插入图片描述
  5. 链表中的第一个元素结点称为首结点,最后一个元素称为尾结点,其指针域(next) 为 空(NULL)。

Ⅱ. 结点类型声明:

typedef itn ElemType;
typedef int Status;
#define OK 1
#define ERRPR 0
typedef struct node
{
	ElemType data;
	struct node *next;
}LNode,*LinkList;

Ⅲ. 基本运算的实现:

1. 初始化单链表:
主要操作:创建一个空的单链表,它只有一个头结点,由L指向它。该结点的next域为空,data域未设定任何值。
代码如下:

Status InitList_(LinkList &L)
{
	L=(LinkList)malloc(sizeof(LNode));
	if(!L)  return ERROR;   //存储分配失败 
	L->next = NULL;               //指针域为空 
	return OK;
 } 

2. 销毁单链表:
代码如下:

Status DestroyList(LinkList &L)
{
	LinkList p;
	while(L)
	{
		p = L->next;
		free(L);
		L = p;
	}
	return OK;
}

3. 求单链表长度:
主要操作:设置一个整型变量i作为计数器,i初值为0,p初始时指向第一个数据据点。然后眼next域逐个往后查找,每一动一次,i值增1.当p所指结点为空时,结束这个过程,i的值即为表长。
代码如下:

int ListLength(LinkList L)
{
	int i=0;
	LinkList p = L->next;  //p指向第一个结点
	while(p)  //没到表尾 
	{
		i++;
		p = p-<next; 
	} 
	return i;
}

4. 求单链表中值为e的元素:
主要操作:用p从头开始遍历单链表L中的结点,用计数器i统计遍历过的结点,其初值为0.在遍历时,若p不为空,则p所指的结点即为要找的结点,查找成功,算法返回次序i。否则算法返回0表示未找到这样的结点。
代码如下:

int LocateElem(LinkList L,ElemType e)
{
	int i = 0; 
	LinkList p = L->next;
	while(p)
	{
		++i;
		if(p->data == e)
			return i;
		p=p->next;
	}
	return 0;
}

5. 单链表的插入:
主要操作:
· 在单链表L中第i个位置上插入值为x的结点。
· 现在单链表L中查找第i-1个结点,若未找到返回0.
· 找到后由p指向该结点,创建一个以x为值的新结点s,将其插入到p结点之后。
代码如下:

Status ListInsert_L(LinkList &L, int i,ELemType e)
{
	LinkList p,s;
	p = L;
	int j = 0;
	while(p && j < i-1)  //寻找第i-1个结点 
	{
		p = p->next;
		++j;
	}
	if(!p || j < i-1)   //i小于1或者大于表长 
		return ERROR;
	s = (LinkList)malloc(sizeof(LNode));  //生成新结点
	s->data = e; 
	s->next = p->next;    //插入L中 
	p->next = s;
	return OK;
} 

6. 单链表的删除:
主要操作:·在单链表L中删除第i个结点;
·先在单链表L中查找第i-1个结点,若未找到返回0.
· 找到后由p指向该结点,然后让q指向后继结点(即要删除的结点)
· 若q所指结点为空则返回0,否则删除q结点并释放其占用的空间
代码如下:

Status ListDelete_L(LinkList &L, int i,ELemType &e)
{
	LinkList p,q;
	p = L;
	int j = 0;    //寻找第i个结点,并令p指向其前驱 
	while(p->next && j < i-1)
	{
		p = p->next;
		++j;
	}
	if(!(p->next) || j > i-1)
		return ERROR;       //删除位置不合理 
	q = p->next;
	p->next = q->next;    //删除并释放结点 
	free(q);
	return OK;
} 

(二). 循环单链表:

  1. 循环单链表是单链表的变形。链表尾结点的next指向不是NULL,而是指向了单链表的前端
    在这里插入图片描述
  2. 为简化操作,在循环单链表中往往加入头结点
    在这里插入图片描述
  3. 循环单链表的判空条件是:L->next == L;
  4. 单链表的特点是:只要知道表中某一结点的地址,就可搜寻到所有其他结点的地址。
  5. 在搜寻过程中,没有一个结点的next域为空

(三). 双向链表:

Ⅰ. 概念:
  1. 双向链表是指在前驱和后继方向都能遍历的线性链表。双向链表每个结点的结构为:在这里插入图片描述
  2. 双向链表中用两个指针表示结点间的逻辑关系
  3. 指向其前驱结点的指针域prior
  4. 指向其后继结点的指针域next
Ⅱ. 双向链表的类型声明:
typedef struct DuLNode
{
	ElemType data;
	DuLNode *prior,*next;
}DuLNode,*DuLinkLIst;

与单链表一样,双向链表也分为非循环双向链表(简称为双向链表)双向循环链表

Ⅲ. 双向循环链表的基本运算:

1. 长度计算:
主要操作:与单循环链表的求表长算法完全相同
代码如下:

int ListLength(DuLinkList L)
{
	int i=0;
	DuLinkList p=L->next;   //p指向第一个结点
	while(p!=L)     //p没到表头 
	{
		i++;
		p=p->next;
	}
	return i; 
}

2. 插入:
主要操作:先在双向循环链表中查找第i-1个结点,若成功,找到该结点(由p所指向),创建一个以e为值的新结点s,将s结点插入到p之后即可。
代码如下:

Status ListInsert(DuLinkList,int i,ElemType e)
{
	DuLinkList p,s;
	if(i<1||i>ListLength(L)+1)   //i值不合法
		return ERROR;
	p=GetElemP(L,i-1);          //在L中确定第i个元素前驱的位置指针p 
	if(!p)
		return ERROR;
	s=(DuLinkList)malloc(sizeof(DuLNode));
	if(!s)
		exit(OVERFLOW);
	s->data=e;
	s->prior=p;           //在第i-1个元素之后插入 
	s->next=p->next;
	p->next->prior=s;
	p->next=s;
	return OK;
 } 

3. 删除:
主要操作:先在双向循环链表中查找第i个结点,若成功,找到该结点(由p所指向),通过前驱结点和后继结点的指针域改变来删除p结点
代码如下:

Status ListDelete(DuLinkList,int i,ElemType &e)
{
	DuLinkList p;
	if(i<1)   //i值不合法
		return ERROR;
	p=GetElemP(L,i);          //在L中确定第i个元素的位置指针p 
	if(!p)
		return ERROR;
	e=p->data;
	p->prior->next=p->next;
	p->next->prior=p->prior;
	free(p);
	return OK;
 } 
发布了17 篇原创文章 · 获赞 19 · 访问量 615

猜你喜欢

转载自blog.csdn.net/So_cute_SJM/article/details/104049990