数据结构2-线性表-知识点总结与梳理

线性表

  • 0.线性表基本操作的思维导图
  • 1.什么是线性表
  • 2.线性表的顺序存储
    • 2.1顺序表的特点
    • 2.2顺序表的存储表示
    • 2.3顺序表的常用操作
      • 2.3.1创建
      • 2.3.2查找
      • 2.3.3插入
      • 2.3.4删除
  • 3.线性表的链式存储
    • 3.1链表的存储特点
    • 3.2单向链表(带头节点)的常见操作
      • 3.2.1节点结构体和单向链表的初始化
      • 3.2.2基础的遍历操作(计算长度,清空链表)
      • 3.2.3插入操作
      • 3.2.4删除操作
      • 3.2.5其他操作(链表的思想)
      • 3.3循环链表
      • 3.4双向链表
  • 4.顺序存储和链式存储的比较
    • 4.1操作方面
    • 4.2存储方面
    • 4.3存取方面


0.线性表基本操作的思维导图

数据结构2-线性表-知识点总结与梳理_第1张图片

1.什么是线性表

    线性表之所以被称作线性表,自然是因为其存储形式是线性的。通俗来讲,就是对于表中的每一个元素(除了第一个和最后一个元素),他们前面都有且只有一个元素(直接前驱),他们的后面也有且只有一个元素(直接后继)。
    线性表具有如下特点:

  • 线性表中的每一个元素都具有相同的数据类型
  • 线性表中的每一个元素都有“位置”和“值”。位置又称下标,决定了该数据元素在表中的位置和前驱、后继的逻辑关系。

2.线性表的顺序存储

2.1顺序表的特点

    顺序存储,简言之就是各个元素的逻辑顺序与其存放的物理顺序一致。在顺序表中,可以对其所有元素随机访问,也就是说,可以通过下标对表中任意元素进行访问操作。
在C语言中的一维数组就是一种顺序存储的线性表,但是顺序表不一定是一维数组。

2.2顺序表的存储表示

    顺序表的存储分静态存储和动态存储两类。其区别是:

  • 静态存储的线性表一旦存满,此时存储空间不能扩充,再加入新的元素就会造成数据溢出,导致程序出错。
  • 动态存储的线性表一旦存满,此时存储空间可以扩充。具体操作为开辟一块新的、更大的连续空间,以代换原来的存储空间。(注意,是开一块全新的空间,而不是在原本的空间上往后拓展,相当于重新malloc)

2.3顺序表的常用操作

2.3.1创建

首先给出顺序表的结构体。

#include 
#include 
const int maxN=30;
typedef int DataType;//元素的数据类型(假设为int)
typedef struct{
	DataType data[maxN];//存储元素
	int n;//表示当前表中的元素个数
}

2.3.2查找

顺序表L,查找里面有无元素x,返回元素x的位置。若没找到,返回-1。

int search(SeqList& L,DataType x){//&符号表示引用,传进去就不是形参,可以理解为传地址进去
	for(int i = 0;i < L.n; i++){
		if(L.data[i] == x) return i;
	}
	return -1;
}

2.3.3插入

先来看一看插入操作的实现思路,就是要把这个位置之后的元素依次向后挪(可以理解为往后站给他让个位置),然后再将新的元素插入进来。
数据结构2-线性表-知识点总结与梳理_第2张图片
代码实现

int Insert(SeqList& L,int i,DataType x){
	//把x插入第i个位置,成功返回1,失败返回-1
	if(L.n==maxN) return -1;//顺序表已满
	if(i<1||i>n+1) return -1;//插入位置不合法
	for (int j=L.n;j>=i;j--){
		L.data[j]=L.data[j-1];
	}
	L.data[i-1]=x;
	L.n++;
	return 1;
}

2.3.4删除

思路和插入类似。我们只需要把需要删除的那个位置往后的元素一次往前挪就可以了。可以理解为排队时一个人走了,后面的人就上前把他的位置补上。
数据结构2-线性表-知识点总结与梳理_第3张图片
代码实现

int Remove(SeqList& L,int i,DataType& x){
	//删除第i个元素,并通过x返回删除掉的元素的值
	//若操作成功返回1,失败返回-1
	if(!L.n)return -1;//为空
	if(i<1||i>n+1) return -1;//删除位置不合法
	for(int j=i;j<L.n;j++){
		L.data[j-1]=L.data[j]
	}
	L.n--;
	return 1;
}

3.线性表的链式存储

3.1链表的存储特点

    在链表中,各元素的存储地址不再是连续的,他们的物理顺序和逻辑顺序并无关系。事实上,因为它为每个数据元素都附加了一个链接指针。通过这些指针,各个数据元素就按照逻辑顺序被勾连了起来。链表具有如下的特点:

  • 链表的长度可以随机扩充。只需要申请一个新的节点,再将其链接到已有链表上即可。
  • 链表元素不可以随机访问。只能通过链接指针从头到尾依次访问。
  • 链表可以通常情况下可以带头节点(就是不存储数据,但放在第一位的一个节点),也可以不带。但为了方便操作我们一般采用带头节点的链表。
        链表的应用实际上十分灵活,这里只介绍其中最常用的操作。具体的示意图如下。
    数据结构2-线性表-知识点总结与梳理_第4张图片
        具体的代码操作请见下文。

3.2单向链表(带头节点)的常见操作

3.2.1节点结构体和单向链表的初始化

首先,我们知道链表是由各个节点构成的,而节点实际上就是一个结构体,包含数据(data)和指向下一个节点位置的指针(next)。

#include 
#include 
typedef int DataType;//数据类型为DataType,这里假设为int
typedef struct node{
	DataType data;
	struct node* next;
}Node;

接下来写一个函数,只需要传入图4中的head,就可以创建一个只有头节点的空链表。

void initList(Node*& head) {
	//初始化单链表,建立只有头节点的空链表
	head = (Node*)malloc(sizeof(Node));
	if (head == NULL) {
		printf("内存分配错误");
		exit(1);
	}
	head->next = NULL;
}

在这里有一点需要注意,传入的是Node*& head而不是Node* head。虽然head是一个指针,但我们要对指针本身进行改动,而不是对指针所指向的元素进行改动。因此,我们可以把Node*看成一种新的数据类型,添加引用符号&可以直接对指针本身进行修改。否则函数内部对指针本身的改动并不会影响外界。

3.2.2基础的遍历操作(计算长度,清空链表)

计算链表的长度,即存储的data 的个数。

int Length(Node* head) {
	Node* p = head->next;
	int cnt = 0;
	while (p != NULL) {
		cnt++;
		p = p->next;
	}
	return cnt;
}

清空链表,注意要把每一个清除的节点占用的内存空间释放掉。

void clearList(Node* head) {
	Node* p;
	while (head->next != NULL) {
		p = head->next;
		head->next = p->next;
		free(p);
	}
}

3.2.3插入操作

void Insert(Node* head, int i, DataType x) {
//将新元素x插入表中第i个位置
	//首先找到第i-1个元素的位置
	Node* p = head;
	int cnt = 0;
	while (cnt < i - 1) {
		cnt++;
		p = p->next;
	}
	//创建一个新的节点
	Node* tmp = (Node*)malloc(sizeof(Node));
	tmp->data = x;
	tmp->next = p->next;
	p->next = tmp;
}

3.2.4删除操作

void Remove(Node* head, int i, DataType& x) {
//删去第i个位置的节点,并将data通过x返回出来
	//首先找到第i-1个元素的位置
	Node* p = head;
	int cnt = 0;
	while (cnt < i - 1) {
		cnt++;
		p = p->next;
	}
	//重新拉链
	Node* tmp = p->next;
	p->next = tmp->next;
	x = tmp->data;
	free(tmp);
}

3.2.5其他操作(链表的思想)

本文并没有详细介绍链表具体如何存入数据。因为存入数据的的操作会根据实际的情况而有很大的差异。
但链表的思想是相通的。当需要存入新的元素是,就向系统要一小块空间创建一个新的Node,然后把数据存进去。最后将这个新的Node通过相邻节点的next指针链接上去。

3.3循环链表

循环链表顾名思义就是有个循环呗。我们知道,通常单向链表的结尾的指针域为NULL,代表链表结束了。但在循环链表中,最后一个不是NULL,而是指向前面的节点(不一定要是第一个节点)。下图是一个示例:
数据结构2-线性表-知识点总结与梳理_第5张图片

3.4双向链表

双向链表相比于单链表,又多了一个指针域,允许每一个节点指向它的后一个元素,也允许每一个节点指向它的前一个元素。

#include 
#include 
typedef int DataType;//数据类型为DataType,这里假设为int
typedef struct node{
	DataType data;
	struct node* next;
	struct node* pre;
}Node;

4.顺序存储和链式存储的比较

4.1操作方面

顺序存储的操作比起链式存储来说,确实方便了很多。比如在C语言有现成的数据类型——数组,而指针真令人头秃。。。

4.2存储方面

  • 顺序表需要一块连续的空间,而链表由于逻辑顺序与物理顺序无关,所以可以使用分散的零碎的空间。从这一点来看,链表可以充分的利用内存中的空间。但是,链表每个数据元素都需要附加一个指针域来标记各个元素之间的逻辑关系,这也会造成内存的浪费。
  • 顺序表的扩充是很不方便的。静态顺序表无法进行扩充,动态顺序表的扩充需要大量数据的移动,效率很低。但链表没有这个问题,只要还有存储空间可以分配,就没有存储溢出的问题。
  • 顺序表的大小需要事先定义,容易造成一部分空间的长期闲置得不到充分利用,而链表没有这一问题。

4.3存取方面

  • 链表的遍历很不方便,只能通过相邻节点的链接,所以不可以随机访问。所以在频繁查找的问题中,顺序表相较而言更有优势。
  • 插入和删除操作中,链表只需要修改链接指针,而顺序表需要移动大量数据。从这一点来看,链表更适合插入和删除的任务。

你可能感兴趣的:(数据结构,数据结构,c++,c语言)