1、线性表的定义与特点

线性结构特性:

若结构是非空有限集,除第一个数据元素无直接前驱,最后一个数据元素无直接后继之外,其他每个数据元素都有一个直接前趋和一个直接后继。

典型线性结构:

线性表、堆栈、队列、字符串、数组等等。

线性表:

数据元素虽然不同,但同一线性表中的元素必定具有相同的特性,即属于同一数据对象,相邻数据元素之间存在着序偶关系。诸如此类由n (n≥0)个数据特性相同的元素构成的有限序列称为线性表。线性表中元素的个数n (n≥0)定义为线性表的长度,n =0时称为空表

对于非空的线性表或线性结构,其特点是:

  1. 存在唯一的一个被称作“第一个”的数据元素;
  2. 存在唯一的一个被称作“最后一个”的数据元素;
  3. 除第一个之外,结构中的每个数据元素均只有一个前驱;
  4. 除最后一个之外,结构中的每个数据元素均只有一个后继。

2、线性表的类型定义

线性表的抽象数据类型定义:
在这里插入图片描述

在这里插入图片描述


3、线性表的顺序实现和表示

①.线性表的顺序存储表示

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表(Sequent ialList)。其特点是:逻辑上相邻的数据元素,其物理次序也是相邻的。

如果线性表的每个元素需占用L个存储单元:

  • LOC(ai)表示ai的存储地址
  • LOC(a1)表示a1的存储地址,称为线性表的起始位置或基地址
  • LOC(ai+1) = LOC(ai)+L
  • LOC(ai) = LOC(a1)+(i-1)*L

确定了存储线性表的起始位置,线性表中任一数据元素都可随机存取。线性表的顺序存储结构是一种随机存取的存储结构。

在这里插入图片描述

//顺序表的存储结构
#define MAXSIZE 100  //顺序表最大长度 
typedef struct
{
	ElemType *elem;  //存储空间的基地址 
	int Length;		 //当前长度 
 } SqList;           //顺序表的结构类型为 SqList

②.顺序表中基本操作的实现

顺序表的初始化

  1. 为顺序表L动态分配一个预定义大小的数组空间,使elem指向这段空间的基地址。
  2. 将表的当前长度设为0。
Status InitList(SqList &L)         //构造一个空的顺序表L
{
    L.elem=new ElemType[MAXSIZE];  //为顺序表分配空间
    if(!L.elem) exit(OVERFLOW);    //存储分配失败
    L.length=0;				       //空表长度为0
    return OK;
}

顺序表的取值

  1. 判断指定的位置序号i值是否合理( 1≤i≤L.length ),若不合理,则返回ERROR。
  2. 若i值合理,则将第i个数据元素L.elem[i-1]赋给参数e,通过e返回第i个数据元素的传值。
Status GetElem(SqList L,int i,ElemType &e)
{
    if(i<1||i>L.length)
        return ERROR;
    e = L.elme[i-1];
    return OK;
}

顺序表的查找

  1. 从第一个元素起,依次和e相比较,若找到与e相等的元素L.elem[i],则查找成功,返回该元素的序号i+1。
  2. 若查遍整个顺序表都没有找到,则查找失败,返回0。

在这里插入图片描述

int LocateElem(SqList L,ElemType e)
{        
    for (i=0;i< L.length;i++)  //i为下标序号
    	if (L.elem[i]==e) 
	        return i+1;  //查找成功,返回序号i+1                
    return 0;  //查找失败,返回0
}

在查找时,为确定元素在顺序表中的位置,需和给定值进行比较的数据元素个数的期望值称为查找算法在查找成功时的平均查找长度(Average Search L ength, ASL)。

顺序表的插入

  1. 判断插入位置i是否合法( i值的合法范围是1≤i≤n+1),若不合法则返回ERROR。
  2. 判断顺序表的存储空间是否已满,若满则返回ERROR。
  3. 将第n个至第i个位置的元素依次向后移动一个位置,空出第i个位置(i=n+1时无需移动)
  4. 将要插入的新元素e放入第i个位置。
  5. 表长加1。

在这里插入图片描述

在这里插入图片描述

Status ListInsert(SqList &L,int i,ElemType e)
{
    if (i < 1 || i > L.length + 1)
        return ERROR;
    if (L.length == MAXSIZE)
        return ERROR;
    for (j = L.length - 1; j >= i - 1; j--)
        L.elem[j + 1] = L.elem[j];
    L.elem[i - 1] = e;
    ++L.length;
    return OK;
}

在这里插入图片描述

顺序表的删除

  1. 判断删除位置i是否合法(合法值为1≤i≤n),若不合法则返回ERROR。
  2. 将第i+1个至第n个的元素依次向前移动一个位置( i=n时无需移动)。
  3. 表长减1。

在这里插入图片描述
在这里插入图片描述

Status ListDelete(SqList &L, int i)
{
    if (i < 1 || i > L.length)
        return ERROR;
    for (j = i; j <= L.length - 1; j++)
        L.elem[j - 1] = L.elem[j];
    --L.length;
    return OK;
}

在这里插入图片描述

③.顺序表的顺序表示和实现实例

测试样例:book.txt

在这里插入图片描述

#include<iostream>
#include<fstream>
#include<string>
#include<iomanip>
using namespace std;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status; //Status 是函数返回值类型,其值是函数结果状态代码。
typedef int ElemType; //ElemType 为可定义的数据类型,此设为int类型

#define MAXSIZE 100			//顺序表可能达到的最大长度
struct Book {
	string id;//ISBN
	string name;//书名
	double price;//定价
};
typedef struct {
	Book *elem; //存储空间的基地址
	int length; //当前长度
} SqList;

Status InitList_Sq(SqList &L) { //顺序表的初始化
	//构造一个空的顺序表L
	L.elem = new Book[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的数组空间
	if (!L.elem)
		exit(OVERFLOW); //存储分配失败退出
	L.length = 0; //空表长度为0
	return OK;
}

Status GetElem(SqList L, int i, Book &e) {//顺序表的取值
	if (i < 1 || i > L.length)
		return ERROR; //判断i值是否合理,若不合理,返回ERROR
	e = L.elem[i - 1]; //elem[i-1]单元存储第i个数据元素
	return OK;
}

int LocateElem_Sq(SqList L, double e) { //顺序表的查找
	//顺序表的查找
	for (int i = 0; i < L.length; i++)
		if (L.elem[i].price == e)
			return i + 1;//查找成功,返回序号i+1
	return 0;//查找失败,返回0
}

Status ListInsert_Sq(SqList &L, int i, Book e) { //顺序表的插入
	//在顺序表L中第i个位置之前插入新的元素e
	//i值的合法范围是1<=i<=L.length+1
	if ((i < 1) || (i > L.length + 1))
		return ERROR; //i值不合法
	if (L.length == MAXSIZE)
		return ERROR; //当前存储空间已满
	for (int j = L.length - 1; j >= i - 1; j--)
		L.elem[j + 1] = L.elem[j]; //插入位置及之后的元素后移
	L.elem[i - 1] = e; //将新元素e放入第i个位置
	++L.length; //表长增1
	return OK;
}

Status ListDelete_Sq(SqList &L, int i) { //顺序表的删除
	//在顺序表L中删除第i个元素,并用e返回其值
	//i值的合法范围是1<=i<=L.length
	if ((i < 1) || (i > L.length))
		return ERROR; //i值不合法
	for (int j = i; j <= L.length; j++)
		L.elem[j - 1] = L.elem[j]; //被删除元素之后的元素前移
	--L.length; //表长减1
	return OK;
}

int main() {
	SqList L;
	int i = 0, temp, a, c, choose;
	double price;
	Book e;
	string head_1, head_2, head_3;
	cout << "1. 建立\n";
	cout << "2. 输入\n";
	cout << "3. 取值\n";
	cout << "4. 查找\n";
	cout << "5. 插入\n";
	cout << "6. 删除\n";
	cout << "7. 输出\n";
	cout << "0. 退出\n\n";

	choose = -1;
	while (choose != 0) {
		cout << "请选择:";
		cin >> choose;
		switch (choose) {
		case 1://创建顺序表
			if (InitList_Sq(L))
				cout << "成功建立顺序表\n\n";
			else
				cout << "顺序表建立失败\n\n";
			break;
		case 2: {//顺序表信息输入
			i = 0;
			L.elem = new Book[MAXSIZE];
			if (!L.elem)
				exit(OVERFLOW);
			L.length = 0;
			fstream file;
			file.open("book.txt");
			if (!file) {
				cout << "错误!未找到文件!" << endl;
				exit(ERROR);
			}
			file >> head_1 >> head_2 >> head_3;
			while (!file.eof()) {
				file >> L.elem[i].id >> L.elem[i].name >> L.elem[i].price;
				i++;
			}
			cout << "输入 book.txt 信息完毕\n\n";
			L.length = i;
			file.close();
		}
			break;
		case 3://顺序表的取值
			cout << "请输入一个位置用来取值:\n";
			cin >> i;
			temp = GetElem(L, i, e);
			if (temp != 0) {
				cout << "查找成功\n";
				cout << "第" << i << "本图书的信息是:\n";
				cout << left << setw(15) << e.id << "\t" << left << setw(50)
						<< e.name << "\t" << left << setw(5) << e.price << endl
						<< endl;
			} else
				cout << "查找失败!位置超出范围\n\n";
			break;
		case 4: //顺序表的查找
			cout << "请输入所要查找价格:";
			cin >> price;
			temp = LocateElem_Sq(L, price);
			if (temp != 0) {
				cout << "查找成功\n";
				cout << "该价格对应的书名为:" << L.elem[temp - 1].name << endl << endl;
			} else
				cout << "查找失败!没有这个价格对应的书籍\n\n";
			break;
		case 5: //顺序表的插入
			cout << "请输入插入的位置和书本信息,包括:编号 书名 价格(用空格隔开):";
			cin >> a;
			cin >> e.id >> e.name >> e.price; //输入a和b,a代表插入的位置,b代表插入的数值(书本信息)
			if (ListInsert_Sq(L, a, e))
				cout << "插入成功.\n\n";
			else
				cout << "插入失败.\n\n";
			break;
		case 6: //顺序表的删除
			cout << "请输入所要删除的书籍的位置:";
			cin >> c;
			if (ListDelete_Sq(L, c))
				cout << "删除成功.\n\n";
			else
				cout << "删除失败.\n\n";
			break;
		case 7: //顺序表的输出
			cout << "当前图书系统信息(顺序表)读出:\n";
			for (i = 0; i < L.length; i++)
				cout << left << setw(15) << L.elem[i].id << "\t" << left
						<< setw(50) << L.elem[i].name << "\t" << left
						<< setw(5) << L.elem[i].price << endl;
			cout << endl;
			break;
		}
	}
	return 0;
}

4、线性表的链式表示和实现

①.单链表的定义和表示

线性表链式存储结构的特点:

用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。

结点:

数据元素的存储映像。由数据域指针域两部分组成。

数据域:

存储数据元素信息。

指针域:

存储反映元素关系的存储位置,称作指针或链。

头指针:

指向第一个结点的指针。

链表:

n个结点由指针链组成一个链表。称为线性表的链式存储结构。

结点只有一个指针域的链表,称为单链表或线性链。最后一个结点的指针域为空,逻辑上相邻的元素在物理上不一定相邻,链表链接的顺序和数据元素的逻辑顺序一致,结点的物理位置可任意安排,不能出现“断链”现象。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

//单链表的存储结构
typedef struct LNode
{
    ElemType data;      //结点的数据域
    sturct LNode *next; //结点的指针域
} LNode, *LinkList;     // LinkList为指向结构体LNode的指针类型

头指针、头结点和首元结点

在这里插入图片描述

在这里插入图片描述
问:头结点的作用?

答:便于首元结点的处理;便于空表和非空表的统一处理(无头结点:L==NULL;有头结点:L->next=NULL)

问:头结点的数据域内装的是什么?

答:头结点的数据域可以为空,也可以存放线性表长度等附加信息,但此结点不能计入链表长度值。

②.单链表基本操作的实现

单链表的初始化

  1. 生成新结点作为头结点,用头指针L指向头结点。
  2. 头结点的指针域置空。
Status InitList_L(LinkList &L)
{                   //构造一个空的单链表L
    L = new LNode;  //生成新结点作为头结点,用头指针L指向头结点
    L->next = NULL; //头结点的指针域置空
    return OK;
}

单链表的取值

  1. 用指针p指向首元结点,用j做计数器初值赋为1。
  2. 从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针 p 不为空(NULL),并且没有到达序号为i的结点,则循环执行以下操作:
    • p指向下一个结点。
    • 计数器j相应加1。
  3. 退出循环时,如果指针p为空,或者计数器j大于i,说明指定的序号i值不合法(i大于表长n或i小于等于0),取值失败返回ERROR;否则取值成功,此时j=i时,p所指的结点就是要找的第i个结点,用参数e保存当前结点的数据域,返回OK。
Status GetElem(LinkList L, int i, ElemType &e)
{
    p = L->next;
    j = 1;
    while(p &&j < i)
    {
        p = p->next;
        ++j;
    }
    if!p || j > i) 
        return ERROR;
    e = p->data;
    return OK;
}

单链表的按值查找

  1. 用指针p指向首元结点。
  2. 从首元结点开始依次顺着链域next向下查找,只要指向当前结点的指针p不为空,并且p所指结点的数据域不等于给定值e,则循环执行以下操作:p指向下一个结点。
  3. 返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL。
LNode *LocateElem(LinkList L, Elemtype e)
{
    p = L->next;
    while (p && p->next != e)
        p = p->next;
    return p;
}

单链表的插入

  1. 查找结点ai-1并由指针p指向该结点。
  2. 生成一个新结点*s。
  3. 将新结点*s的数据域置为e。
  4. 将新结点*s 的指针域指向结点ai。(s->next = p->next)
  5. 将结点p的指针域指向新结点s。(p->next = s)

步骤4、5不能互换,若掉换顺序,后结点丢失,找不到结点并造成内存泄露

在这里插入图片描述

Status ListInsert(LinkList &L, int i, ElemType &e)
{ //在带头结点的单链表L中第i个位置之前插入元素e
    p = L;
    j = 0;
    while (p && j < i - 1)
    {
        p = p->next;
        ++j;
    }
    if (!p || j > i - 1)
        return ERROR;
    s = new LNode;
    s->data = e;
    s->next = p->next;
    p->next = s;
    return OK;
}

单链表的删除

  1. 查找结点ai-1并由指针p指向该结点。
  2. 临时保存待删除结点ai的地址在q中,以备释放。
  3. 将结点*p的指针域指向ai的直接后继结点。
  4. 释放结点ai的空间。

在这里插入图片描述

Status ListDelete(LinkList &L, int i)
{ //在带头结点的单链表 L 中,删除第 i 个元素
    p = L;
    j = 0;
    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;
    delete q;
    return OK;
     
}

前插法单链表

前插法是通过将新结点逐个插入链表的头部(头结点之后)来创建链表,每次申请一个新结点,读入相应的数据元素值,然后将新结点插入到头结点之后。

在这里插入图片描述

  1. 创建一个只有头结点的空链表。
  2. 根据待创建链表包括的元素个数n,循环n次执行以下操作:
    - 生成一个新结点p;
    - 输人元素值赋给新结点
    p 的数据域;
    - 将新结点*p插人到头结点之后。
void CreateList_H(LinkList &L, int n)
{ //逆位序输入 n 个元素的值,建立带表头结点的单链表 L
    L = new LNode;
    L->next = NULL;
    for (i = 0; i < n; ++i)
    {
        p = new LNode;
        cin >> p->data;
        p->next = L->next;
        L->next = p;
    }
}

尾插法单链表

尾插法是通过将新结点逐个插入到链表的尾部来创建链表。同前插法样,每次申请一个新结点,读入相应的数据元素值。不同的是,为了使新结点能够插入到表尾,需要增加一个尾指针r指向链表的尾结点。

在这里插入图片描述

  1. 创建一个只有头结点的空链表。
  2. 尾指针r初始化,指向头结点。
  3. 根据创建链表包括的元素个数n,循环n次执行以下操作:
    - 生成一个新结点p;
    - 输人元素值赋给新结点
    p的数据域;
    - 将新结点p插入到尾结点r之后;
    - 尾指针r指向新的尾结点*p。
void CreateList_L(LinkList &L, int n)
{ //正位序输入n个元素的值,建立带表头结点的单链表L
    L = new LNode;
    L->next = NULL;
    r = L;
    for (i = 0; i < n; ++i)
    {
        p = new LNode;
        cin >> p->data;
        p->next = NULL;
        r->next = p;
        r = p;
    }
}

循环列表

表中最后一个结点的指针域指向头结点,整个链表形成一个环。

在这里插入图片描述

循环列表的任何一个位置可以找到其他所有结点。

在这里插入图片描述
循环列表的合并

在这里插入图片描述
双向链表

结点有两个指针域的链表,称为双向链表。

typedef struct DuLNode
{
    ElemType data;
    struct DuLNode *prior;
    struct DuLNode *next;
} DuLNode, *DuLinkList

在这里插入图片描述
双向链表的插入

在这里插入图片描述

Status ListInsert_DuL(DuLinkList &L, int i, ElemType e)
{
    if (!(p = GetElem_Dul(L, i)))
        return ERROR;
    s = new DuLNode;
    s->date = e;
    s->prior = p->prior;
    p->prior->next = s;
    s->next = p;
    p->prior = s;
    return OK;
}

双向链表的删除

在这里插入图片描述

Status ListInsert_DuL(DuLinkList &L, int i)
{
    if (!(p = GetElem_Dul(L, i)))
        return ERROR;
    p->prior->next = p->next;
    p->next->prior = p->prior;
    delete p;
    return OK;
}

③.单链表的顺序表示和实现实例

测试样例:book.txt

在这里插入图片描述

#include<iostream>
#include<string>
#include<iomanip>
#include<fstream>
using namespace std;

#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status; //Status 是函数返回值类型,其值是函数结果状态代码。
typedef int ElemType; //ElemType 为可定义的数据类型,此设为int类型

struct Book {
	string id;//ISBN
	string name;//书名
	double price;//定价
};
typedef struct LNode {
	Book data; //结点的数据域
	struct LNode *next; //结点的指针域
} LNode, *LinkList; //LinkList为指向结构体LNode的指针类型

string head_1, head_2, head_3;
int length;

Status InitList_L(LinkList &L) { //单链表的初始化
	//构造一个空的单链表L
	L = new LNode; //生成新结点作为头结点,用头指针L指向头结点
	L->next = NULL; //头结点的指针域置空
	return OK;
}

Status GetElem_L(LinkList L, int i, Book &e) { //单链表的取值
	//在带头结点的单链表L中查找第i个元素
	//用e返回L中第i个数据元素的值
	int j;
	LinkList p;
	p = L->next;
	j = 1; //初始化,p指向第一个结点,j为计数器
	while (j < i && p) { //顺链域向后扫描,直到p指向第i个元素或p为空
		p = p->next; //p指向下一个结点
		++j; //计数器j相应加1
	}
	if (!p || j > i)
		return ERROR; //i值不合法i>n或i<=0
	e = p->data; //取第i个结点的数据域
	return OK;
} //GetElem_L

LNode *LocateElem_L(LinkList L, int e) { //按值查找
	//在带头结点的单链表L中查找值为e的元素
	LinkList p;
	p = L->next;
	while (p && p->data.price != e)//顺链域向后扫描,直到p为空或p所指结点的数据域等于e
		p = p->next; //p指向下一个结点
	return p; //查找成功返回值为e的结点地址p,查找失败p为NULL 
} //LocateElem_L

Status ListInsert_L(LinkList &L, int i, Book &e) { //单链表的插入
	//在带头结点的单链表L中第i个位置插入值为e的新结点
	int j;
	LinkList p, s;
	p = L;
	j = 0;
	while (p && j < i - 1) {
		p = p->next;
		++j;
	}//查找第i?1个结点,p指向该结点
	if (!p || j > i - 1)
		return ERROR; //i>n+1或者i<1
	s = new LNode; //生成新结点*s 
	s->data = e; //将结点*s的数据域置为e
	s->next = p->next; //将结点*s的指针域指向结点ai
	p->next = s; //将结点*p的指针域指向结点*s
	++length;
	return OK;
} //ListInsert_L

Status ListDelete_L(LinkList &L, int i) { //单链表的删除
	//在带头结点的单链表L中,删除第i个位置	
	LinkList p, q;
	int j;
	p = L;
	j = 0;
	while ((p->next) && (j < i - 1)) //查找结点,p指向该结点
	{
		p = p->next;
		++j;
	}
	if (!(p->next) || (j > i - 1))
		return ERROR; //当i>n或i<1时,删除位置不合理 
	q = p->next; //临时保存被删结点的地址以备释放 
	p->next = q->next; //改变删除结点前驱结点的指针域 
	delete q; //释放删除结点的空间 
	--length;
	return OK;
} //ListDelete_L

void CreateList_H(LinkList &L, int n) { //前插法创建单链表
	//逆位序输入n个元素的值,建立到头结点的单链表L
	LinkList p;
	L = new LNode;
	L->next = NULL; //先建立一个带头结点的空链表
	length = 0;
	fstream file;
	file.open("book.txt");
	if (!file) {
		cout << "未找到相关文件,无法打开!" << endl;
		exit(ERROR);
	}
	file >> head_1 >> head_2 >> head_3;
	while (!file.eof()) {
		p = new LNode; //生成新结点*p
		file >> p->data.id >> p->data.name >> p->data.price; //输入元素值赋给新结点*p的数据域
		p->next = L->next;
		L->next = p; //将新结点*p插入到头结点之后
		length++;//同时对链表长度进行统计
	}
	file.close();
} //CreateList_F

void CreateList_R(LinkList &L, int n) { //后插法创建单链表
	//正位序输入n个元素的值,建立带表头结点的单链表L 
	LinkList p, r;
	L = new LNode;
	L->next = NULL; //先建立一个带头结点的空链表
	r = L; //尾指针r指向头结点
	length = 0;
	fstream file; //打开文件进行读写操作
	file.open("book.txt");
	if (!file) {
		cout << "未找到相关文件,无法打开!" << endl;
		exit(ERROR);
	}
	file >> head_1 >> head_2 >> head_3;
	while (!file.eof()) { //将文件中的信息运用后插法插入到链表中
		p = new LNode;//生成新结点
		file >> p->data.id >> p->data.name >> p->data.price;//输入元素值赋给新结点*p的数据域
		p->next = NULL;
		r->next = p;//将新结点*p插入尾结点*r之后 		
		r = p;//r指向新的尾结点*p
		length++; //同时对链表长度进行统计
	}
	file.close();
} //CreateList_L

int main() {
	int a, n, choose;
	double price;
	Book e;
	LinkList L, p;
	cout << "1. 建立\n";
	cout << "2. 输入\n";
	cout << "3. 取值\n";
	cout << "4. 查找\n";
	cout << "5. 插入\n";
	cout << "6. 删除\n";
	cout << "7. 输出\n";
	cout << "0. 退出\n\n";

	choose = -1;
	while (choose != 0) {
		cout << "请选择:";
		cin >> choose;
		switch (choose) {
		case 1: //建立一个单链表
			if (InitList_L(L))
				cout << "成功建立链表!\n\n";
			break;
		case 2: //使用后插法创建单链表
			CreateList_R(L, length);
			cout << "输入 book.txt 信息完毕\n\n";
			break;
		case 3: //单链表的按序号取值
			cout << "请输入一个位置用来取值:";
			cin >> a;
			if (GetElem_L(L, a, e)) {
				cout << "查找成功\n";
				cout << "第" << a << "本图书的信息是:\n";
				cout << left << setw(15) << e.id << "\t" << left << setw(50)
						<< e.name << "\t" << left << setw(5) << e.price << endl
						<< endl;
			} else
				cout << "查找失败\n\n";
			break;
		case 4: //单链表的按值查找
			cout << "请输入所要查找价格:";
			cin >> price;
			if (LocateElem_L(L, price) != NULL) {
				cout << "查找成功\n";
				cout << "该价格对应的书名为:" << LocateElem_L(L, price)->data.name
						<< endl << endl;
			} else
				cout << "查找失败! 定价" << price << " 没有找到\n\n";
			break;
		case 5: //单链表的插入
			cout << "请输入插入的位置和书的信息,包括:编号 书名 价格(用空格隔开):";
			cin >> a;
			cin >> e.id >> e.name >> e.price;
			if (ListInsert_L(L, a, e))
				cout << "插入成功.\n\n";
			else
				cout << "插入失败!\n\n";
			break;
		case 6: //单链表的删除
			cout << "请输入所要删除的书籍的位置:";
			cin >> a;
			if (ListDelete_L(L, a))
				cout << "删除成功!\n\n";
			else
				cout << "删除失败!\n\n";
			break;
		case 7: //单链表的输出
			cout << "当前图书系统信息(链表)读出:\n";
			p = L->next;
			while (p) {
				cout << left << setw(15) << p->data.id << "\t" << left << setw(
						50) << p->data.name << "\t" << left << setw(5)
						<< p->data.price << endl;
				p = p->next;
			}
			cout << endl;
			break;
		}
	}
	return 0;
}

5、顺序表和链表的比较


6、例题与应用

在这里插入图片描述
在这里插入图片描述
线性表的典型算法实现:

  1. 假设有两个按元素值递增有序排列的线性表A和B,均以单链表作为存储结构,请编写算法将A表和B表归并成一个按元素值递减有序(即非递增有序,允许表中含有值相同的元素)排列的线性表C,并要求利用原表(即A表和B表)的结点空间构造C表。
  2. 编程实现如下功能:
    (1)根据输入的一系列整数,以0标志结束,用头插法建立单链表,并输出单链表中各元素值,观察输入的内容与输出的内容是否一致。
    (2)在单链表的第i个元素之前插入一个值为x的元素,并输出插入后的单链表中各元素值。
    (3)删除单链表中第i个元素,并输出删除后的单链表中各元素值。
    (4)在单链表中查找第i个元素,如果查找成功,则显示该元素的值,否则显示该元素不存在。

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;

typedef struct Node
{
    int data;
    struct Node *next;
} LNode, *LinkList;

LinkList CreateList() //创建单链表
{
    LinkList L;
    LNode *p;
    int c;
    L = new LNode;
    L->next = NULL; //建立带头结点的空链表
    cin >> c;
    while (c != 0)
    {
        p = new LNode;
        p->data = c;
        p->next = L->next; //将新结点插入到头结点之后
        L->next = p;
        cin >> c;
    }
    return L;
}

void ShowList(LinkList L) //显示单链表
{
    LNode *p;
    p = L->next;      //指向首元结点
    while (p != NULL) //对单链表进行遍历
    {
        cout << p->data << " ";
        p = p->next;
    }
    cout << endl;
}

void ReverseList(LinkList L) //逆置单链表
{
    LNode *p, *q;
    p = L->next;
    L->next = NULL; //将已有的数据元素再次插入,实现逆序
    while (p != NULL)
    {
        q = p->next;
        p->next = L->next;
        L->next = p;
        p = q;
    }
}

LinkList MergeList(LinkList LA, LinkList LB) //合并单链表
{
    ReverseList(LA); //逆置链表LA
    ReverseList(LB); //逆置链表LB
    LinkList LC;
    LNode *pa, *pb, *r;
    pa = LA->next;
    pb = LB->next;
    LC = LA;
    LC->next = NULL;
    r = LC;
    while (pa != NULL && pb != NULL)
    {
        if (pa->data <= pb->data) //比较数据元素大小
        {
            r->next = pb;
            r = pb;
            pb = pb->next;
        }
        else
        {
            r->next = pa;
            r = pa;
            pa = pa->next;
        }

        if (pa)
        {
            r->next = pa;
        }
        else
        {
            r->next = pb;
        }
    }
    return LC;
}

int Insert(LinkList &L, int i, int e) //插入数据元素
{
    LinkList t, p;
    int j = 1;
    t = L;
    if (i < 1)
    {
        cout << "插入错误" << endl;
        return 0;
    }
    if (i == 1) //插入位置在表头
    {
        p = new LNode;
        p->data = e;
        p->next = t->next;
        L->next = p;
        cout << "插入成功" << endl;
        return 0;
    }
    while (j < i - 1 && L) //异常判断
    {
        t = t->next;
        j++;
        if (t == NULL)
        {
            cout << "插入错误" << endl;
            return 0;
        }
    }
    t = t->next; //插在非表头
    p = new LNode;
    p->data = e;
    p->next = t->next;
    t->next = p;
    cout << "插入成功" << endl;
}

int Delete(LinkList &L, int i) //删除数据元素
{
    LinkList p, t;
    int j = 0;
    p = L;
    if (i < 1)
    {
        cout << "删除失败" << endl;
        return 0;
    }
    while (j < i - 1 && L)
    {
        p = p->next;
        j++;
        if (p == NULL)
        {
            cout << "删除失败" << endl;
            return 0;
        }
    }
    t = p->next;
    p->next = t->next;
    cout << "删除成功" << endl;
    delete t;
}

int SecrchList(LinkList &L, int i) //查找数据元素
{
    LinkList p;
    int j = 0, e;
    p = L;
    if (i < 1)
    {
        cout << "查找失败,该元素不存在" << endl;
        return 0;
    }
    while (j < i && L)
    {
        p = p->next;
        j++;
        if (p == NULL)
        {
            cout << "查找失败,该元素不存在" << endl;
            return 0;
        }
    }
    e = p->data;
    cout << "查找成功,第" << i << "个元素为" << e << endl;
}

int main(void)
{
    int i, e, select;

    LinkList LA;
    cout << "请输入单链表A的数据元素:" << endl;
    LA = CreateList();
    getchar();
    cout << "请输入单链表B的数据元素:" << endl;
    LinkList LB;
    LB = CreateList();

    cout << "单链表LA:" << endl;
    ShowList(LA);
    cout << "单链表LB:" << endl;
    ShowList(LB);

    LinkList LC;
    LC = MergeList(LA, LB);
    cout << "合并单链表LA和单链表LB:" << endl;
    ShowList(LC);
    while (1)
    {
        cout << "\n*****1.插入  2.删除  3.查找  4.退出*****\n";
        cin >> select;
        switch (select)
        {
        case 1:
            cout << "输入位置和要插入的元素:" << endl;
            cin >> i >> e;
            Insert(LC, i, e);
            ShowList(LC);
            break;
        case 2:
            cout << "输入要删除的元素的位置" << endl;
            cin >> i;
            Delete(LC, i);
            ShowList(LC);
            break;
        case 3:
            cout << "输入要查找的元素的位置" << endl;
            cin >> i;
            SecrchList(LC, i);
            break;
        case 4:
            return 0;
            break;
        default:
            cout << "输入错误,请重新输入!" << endl;
            break;
        }
    }
    return 0;
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐