数据结构复习--第二章线性表

思维导图

线性表代码

一、顺序表

1.静态定义

静态分配内存空间,通过一维数组定义,数组大小和空间事先已经固定,空间一旦占满,插入新的数据将会溢出,导致程序崩溃。

#define MaxSize 50//定义线性表的最大长度
typedef struct{
	ElementType data[MaxSize];//顺序表的元素
	int length;//顺序表的当前长度
}SqList;//顺序表的类型定义

2.动态定义

动态分配时,存储数组的空间是在程序执行过程中通过动态讯处分配语句分配的,一旦空间被占满,会另外开辟一块更大的存储空间,用来替换原来的存储空间,打到扩容数组空间的目的。

#define InitSize 50//表长度的初始定义
typedef struct{
	ElementType *data;//指示动态分配数组的指针
	int MaxSize,length;//数组的最大容量和当前个数
}SeqList;//动态分配数组顺序表的类型定义

3.malloc函数初始动态分配

C语言

L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);//C语言

C++

L.data=new ElemType[InitSize];//C++

二、顺序表的基本操作

1.插入操作

在顺序表L的第i个位置插入新元素e

bool ListInsert(SqList &L,int i,ElemType e){//此处的&符表示要把值带回来
	if(i<1||i>L.length+1)
		return false;
		//判断i的范围是否有效,i此时代表的是数组下标,数组下标从0开始,故i的取值范围是0到length+1
	for(int j=L.length;j>=i;j--)//将第i个元素之后的元素后移
		L.data[j]=L.data[j-1];
	L.data[i-1]=e;//在位置i处放入新元素e
	L.length++;//线性表长度加一
	return true;
}

注:区别顺序表的位序(从1开始)和数组下标(从0开始)
位序的范围是1到length
数组下标范围是0到length+1

2.删除操作

删除顺序表L中第i个位置的元素,用引用变量返回,将被删除元素赋给引用变量e,并将第i+1个元素及其后的所有元素一次向前移动一个元素

bool ListDelete(SqList &L,int i,ElemType &e){//此处的e也是引用型,要把e的值返回
	if(i<1||i>L.length+1)
		return false;
		//判断i的范围是否有效,i此时代表的是数组下标,数组下标从0开始,故i的取值范围是0到length+1
	e=L.data[i-1];//将被删除的元素赋值给变量e
	for(int j=i;j<L.length;j++)//将第i个位置之后的元素向前移动
		L.data[j-1]=L.data[j];
	return true;
}

3.按值查找(顺序查找)

在顺序表L中查找第一个元素值等于e的元素,并返回其位序

int LocateElem(SqList L,ElemType e){
	int i;
	for(i=0;i<L.length;i++)
		if(L.data[i]==e)
			return i+1;//i从0开始,下标为i的元素值等于e,返回其位序i+1
	return 0;//退出循环,查找失败
}

4.相关问题

(1)顺序表按序号随机存取
注:存取方式是指读写方式,顺序表是一种支持随机存取的存储结构,根据起始地址加上元素序号,可以很方便地访问任意一个元素
(2)若线性表最常用的操作是存取任一指定序号的元素,则使用线性表
(3)一个顺序表所占用的存储空间大小与元素的存放顺序无关

三、链表

1.单链表

单链表的定义

结点类型描述:

typedef struct LNode{
	ElemType data;//数据域
	struct LNode *next;//指针域
}LNode,*LinkList;

(1)通常通过头指针来标识一个单链表,如单链表L,头指针为NULL时表示一个空表,在单链表第一个结点之前附加一个结点,称为头结点,头结点的指针域指向线性表的第一个元素结点。
(2)不管有没有头结点,头指针都始终指向链表的第一个节点,而头结点是带头结点的链表中的第一个结点,结点内通常不存储信息。
(3)增加头结点的目的是:方便运算的实现。

建表

头插法建表

从一个空表开始,生成新结点,将读取到的数据存放到新结点的数据域(data)中,然后将新结点插入到当前表的表头,即不断的向头结点之后插入新结点建表。
数据结构复习--第二章线性表_第1张图片
由此可以看出读入数据的顺序与生成的链表中的元素顺序是相反的,每个结点插入的时间为O(1),设单链表表长为n,则总时间复杂度为O(n)

LinkList CreateListHead(LinkList &L){//逆向建立单链表L,每次均在头结点之后插入元素
	LNode *s;//始终指向新结点的指针
	int x;//插入元素类型
	//1.创建头结点
	L=(LinkList)malloc(sizeof(LNode));
	//2.初始化为空链表
	L->next = NULL;
	//3.输入元素值
	while(x != 9999){//输入9999表示输入结束
	//4.创建新结点
		s=(LNode*)malloc(sizeof(LNode));
		s->data = x;//将输入的元素值存入data域
		s->next = L->next;//L为头指针
		L->next = s;
	}
	return L;
}
尾插法建表

同理,将新结点插入到当前表的表尾,即不断的向尾结点之后插入新结点建表。

LinkList CreateListTail(LinkList &L){//单链表L,每次均在表尾结点之后插入元素
	int x;//插入元素类型
	//1.创建尾结点
	L=(LinkList)malloc(sizeof(LNode));
	//2.创建表头指针和表尾指针,并将表尾指针指向新结点
	LNode *s,*r = L;
	//3.输入元素值
	while(x!=9999){//输入9999表示输入结束
	//4.创建新结点
		s=(LNode*)malloc(sizeof(LNode));
	//5.将新结点插入表尾
		s->data = x;
		r->next = s;
		r = s;//r指向新的表尾结点
	}
	//6.尾结点指针置空
	r->next = NULL;
	return L;
}

按序号查找结点值

从单链表的第一个结点出发,顺着next域逐个往下查找,知道找到第i个为止,否则返回最后一个结点指针域NULL

LNode *GetElem(LinkList L,int i){//i为结点个数
	int j = 1;
	LNode *p = L->next;//头结点指针赋给p
	if(i==0)//若i为0,返回头结点
		return L;
	if(i<1)//若i位置不合法,返回空
		return NULL;
	while(p&&j<i){//从第一个结点开始遍历,一直找到第i个结点
		p = p->next;//头结点指针依次向后遍历
		j++;//序号加一
	}
	return p;//返回第i个结点的指针;如果i大于表长,此时p=null,直接返回p即可
}

按值查找表结点

从单链表的第一个结点开始,由前往后依次比较表中和节点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表没有这样的结点,则返回NULL

LNode *LocateElem(LinkList L,ElemType e){
//取带头结点的单链表L中数据域等于e的结点指针,若没有这个结点,返回NULL
	LNode *p = L->next;//将头结点赋值给p
	while(p!=NULL&&p->next!=e){//从第1个结点开始查找数据域等于e的结点
		p = p->next;//依次向后查找
	}
	return p;//找到后返回改指针结点,否则返回NULL
}

插入结点

将值为x的新结点插入到单链表的第i个位置上。先检查插入位置是否合法,然后找到待插入位置的前驱结点,即第i-1结点,再在其后插入新结点。

数据结构复习--第二章线性表_第2张图片

//前插
//1.查找插入结点的前驱位置
p = GetElem(L,i-1);
//2.在节点p后插入结点s
s->next = p->next;
p->next = s;

后插:在单链表插入中通常采用后插操作,将前插转换为后插:即在进行完前插操作后,交换p和s的数据域,相当于在p结点存储的数据之后插入数据

//同前插操作
s->next = p->next;
p->next = s;
//再交换数据域
temp = p->data;
p-data = s->data;
s->data = temp;

删除结点

删除单链表的第i个结点,先检查删除位置是否合法,然后找到待插入位置的前驱结点,即第i-1结点,再将其删除。
数据结构复习--第二章线性表_第3张图片

//删除给定结点后一结点*q
	p = GetElem(L,i-1);//查找删除结点的前驱结点
	q = p->next;//让q指向被删除结点
	//将*q结点从链中断开
	p->next = q->next;
	//释放q结点
	free(q);
	

删除所给结点:从链表表头的头结点开始顺序找到其前驱结点,然后执行删除操作。也可以通过删除其后继结点来进行操作,类似前面的向后插入,将后继结点的值赋值给要删除的p,再将后继结点删除
数据结构复习--第二章线性表_第4张图片

//删除所给结点*p
	//令q指向*p的后继结点
	q = p->next;
	//和后继结点交换数据域
	p->data = p->next->data;
	//将q结点从链中断开
	p->next = q->next;
	//释放q结点
	free(q);

2.双链表

由于单链表结点只有一个只想其后继的指针,是的单链表只能从头结点依次向后遍历。如果要访问某个结点的前驱结点,就只能从头开始遍历。因此访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。

为了能更方便的就访问到某个结点的前驱结点和后继结点,引入双链表,双链表中有两个指针prior和next,分别指向该节点的前驱结点和后继结点。

双链表结点定义

typedef struct DNode{//定义双链表结点类型
	ElemType data;//数据域
	struct DNode *prior,*next;//前驱和后继指针
}DNode,*DLinkList;

双链表的插入操作

在p所指结点之后插入结点*q
数据结构复习--第二章线性表_第5张图片

//将*q结点插入到*p之后
q->next = p->next;//1
p->next->prior = q;//2
q->prior = p;//3
p->next = q;//4
//1.2步必须在4步之前,否则*p后继结点的指针会丢掉

在p所指结点之前插入结点q
数据结构复习--第二章线性表_第6张图片
q插到*p之前

p->proir->next=q;//1
q->next=p;//2
q->proir=p->prior;//3
p->prior=q;//4

双链表删除结点

删除双链表p的后继结点q
数据结构复习--第二章线性表_第7张图片

//删除*p的后继结点*q
p->next = q->next;
q->next->prior = p;
free(q);

3.循环链表

(1)循环单链表

循环单链表与单链表的区别在于,最后一个结点的指针不是NULL,而是改为指向头结点(表尾指针*r的next域指向L),故表中没有指针域为NULL的结点。

因此判断循环单链表是否为空的条件是看头结点的指针是否等于头指针,而不是看头指针是否为空。
数据结构复习--第二章线性表_第8张图片

(2)循环双链表

循环单链表退出循环单链表,增加的是,头结点的指针prior指针还要指向表尾结点

若结点*p为尾结点时,p->next==L;
当循环双链表为空表时,其头结点的prior和next域都等于L
数据结构复习--第二章线性表_第9张图片
数据结构复习--第二章线性表_第10张图片

4.静态链表

静态链表借助数组来描述线性表的链式存储结构,结点有数据域data和指针域next。

与前面所讲的链表中的指针不同的是,这里的指针是结点的相对地址(数组下标,指示下一个元素在数组中的下标,简称游标

和顺序表一样,静态链表也要预先分配一块连续的内存空间(较大的空间)

静态链表的结构类型定义:

#define MaxSize 50//静态链表的最大长度
typedef struct{
	ElemType data;//存储数据元素
	int next;//下一个元素的数组下标
}SLinkList[MaxSize];

你可能感兴趣的:(笔记)