王道数据结构第二章 线性表

2.1线性表的定义和基本操作

2.1.1线性表的定义        Linear List

线性表是具有相同数据类型的n(n>=0)个元素的有限序列,其中n为表长,当n=0时,线性表是一个空表。若用L命名线性表,则其一般表示为

L=(a1,a2,a3......an)。

ai是线性表中第“i"个元素在线性表中的次序。a1是表头元素,an是表尾元素。

位序从1开始,数组下标从0开始

除第一个元素之外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

由于数据类型相同,所以数据元素所占空间一样大,我们可以方便找到每一个数据元素位置。

2.1.2线性表的基本操作

InitList(&L)初始化表。构造一个空的线性表并分配内存空间

DestroyList(&L)销毁操作。销毁线性表,并释放线性表L所占用的内存空间

ListInsert(&L,i,e)插入操作。在表中第i个位置插入指定元素e

ListDelete(&L,i,e)删除操作。删除表中第i个位置的元素,并用e返回删除元素的值。

LocateElem(&L,e)按值查找。在表中查找具有给定关键字元素的值。

GetElem(&L,i)按位查找。获取表中第i个位置元素的值。

Length(L)求表长。返回线性表L的长度,即数据元素的个数。

Print(L)输出操作。按前后顺序输出线性表L所有元素值

Empty(L)判空操作。若L为空表,则返回true,否则返回false。

Tips:

对数据结构的操作——创销、增删改查。

这里的函数接口,参数类型是抽象的,我们并不关心具体类型。

注意什么时候需要引用传参(对参数的修改结果需要带回来)

2.2线性表的顺序表示

2.2.1顺序表的定义

顺序表:用顺序存储的方式实现线性表

逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

如何知道一个数据元素的大小?sizeof(ElemType)

顺序表的实现——静态分配

#define MaxSize 10//定义最大长度
typedef struct{
     ElemType data[MaxSize]//用静态的数组存放数据元素
     int length//顺序表的当前长度
}SqList;//顺序表的类型定义(静态分配方式)
void InitList(SqList&L){
     for(int i=0;i<MaxSize;i++){
         L.data[i]=0; 
     }
     L.length=0;//这一步不能省略,因为内存中可能有之前遗留下来的脏数据
}

如果数组存满了怎么办?

立即推,放弃治疗,顺序表表长确定了就无法更改。

顺序表的实现:动态分配

key:动态申请和释放内存空间。

C语言——malloc free函数     头文件#include<stdlib.h>

L.data=(ElemType)malloc(sizeof(ElemType)*InitSize);

malloc函数返回一个指针,需要强制转化类型。malloc函数参数:指明要分配多大的连续内存空间。

#include<stdlib.h>
using namespace std;
#define InItSize 10//顺序表初始的默认长度
typedef struct {
	int* data;//指向动态分配数组的指针
	int length;//当前长度
	int MaxSize;//最大容量
} SeqList;
void InitList(SeqList& L) {
	//用malloc函数申请一片连续的内存空间
	L.data = (int*)malloc(InItSize * sizeof(int));
	L.length = 0;
	L.MaxSize = InItSize;
}
//增加动态数组的长度
void IncreaseSize(SeqList& L,int len) {
	int* p = L.data;
	L.data = (int*)malloc(sizeof(int) * (InItSize + len));
	for (int i = 0; i < L.length; i++) {
		L.data[i] = p[i];//将数据复制到新区域
	}
	L.MaxSize = InItSize + len;//顺序表最大长度增加
	free(p);//释放原来的内存空间
}
int main() {
	SeqList L;//声明顺序表
	InitList(L);//初始化顺序表
	IncreaseSize(L, 5);
	return 0;
}

C++:new delete关键字。

顺序表特点:随机访问,O(1)时间找到第i个元素。存储密度高,拓展容量不方便,即便是动态数组,也需要很大时间复制元素。删除增加元素更加不方便。

2.2.2顺序表上基本操作的实现

1.插入

ListInsert(&L,i,e)插入操作,在表L中第i个位置插入指定元素.

此刻的i指的是位序,顺序表的位序是从1开始的,但是数组下标是从0开始。

bool ListInsert(SqList& L, int i, int num) {
	if (i<1 || i>=L.length + 1) {//插入的i不合法
		return false;
	}
	if (L.length>=MaxSize) {//当前位置已满无法存储
		return false;
	}
	for (int j = length, j >= i, j--) {//i之后所有元素后移一位
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = num;//插入自己想要的元素
	L.length++;
	return ture;
}

线性表插入操作时间复杂度为O(n)

2.删除操作

bool ListRemove(SqList& L, int i, int &num) {
	if (i < 1 || i >= L.length + 1) {//i不合法
		return false;
	}
	num=L.data[i - 1] ;
	for (int j = i, j <length, j++) {//i之后所有元素向前移一位
		L.data[j-1] = L.data[j ];
	}
	L.length--;
	return ture;
}

注意此次变量num是引用传参,修改带回了。

同时,我们进行删除操作时,元素从前面的开始移,进行插入操作时,元素从最后面的开始移动

2.2.3顺序表的查找

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

LocateElem(L,i)在表中找到具有给定关键字的元素。

int LocateElem(SqList L, int e) {
	for (int i = 0; i < length; i++) {
		if (L.data[i] == e) {
			return i+1;//我们返回的是位序
		}
	}
	return 0;
}

顺序查找时间复杂度为O(n).如果需要判断两个结构类型是否相等,C语言必须分别看结构每个元素是否对应,C++可以重载==运算符(考试时用==运算符是可以的,除非明确说明C语言程序设计

2.按位查找

GetElem(L,i)获取顺序表L第i个位置的元素的值。

int GetElem(SqList L, int i) {
	if (i < 1 || i >= L.length + 1) {//i不合法
		return 0;
	}
	return L.data[i - 1];//即便是动态分配内存也可用此方法
}

顺序表特性就是随机存取,时间复杂度O(1)

2.3 线性表的链式表示

2.3.1 单链表的定义

每个结点存储自身的数据,还存放指向下一个节点的指针。

王道书上所说的头节点,实际上是我学过的虚拟头节点方式,最好还是用dummy_head ,便于我们统一操作。

typedef struct LNode{
  ElemType data;
  struct LNode*next;
}LNode,*LinkList;

LNode*与 LinkList本质上是一样的,只不过LNode*强调这是一个结点类型,LinkList强调引入的是一个链表。

创建不带虚拟头结点的链表,判断链表是否为空:L==NULL;

带虚拟头结点的链表判断为空:L->next==NULL;

2.3.2 单链表的插入删除

按位序插入:在第i个位置插入元素e(带虚拟头结点)

bool ListInsert(LinkList& L, int i, int e) {
	if (i < 1)return false;
	LNode* p;//指针p用来扫描
	int j = 0;//记录指针p当前扫描到第几个结点
	p = L;//L指向头节点,头节点是第0个节点
	while (p != NULL && j < i - 1) {//循环找到第i-1个节点
		j++;
		p = p->next;
	}
	if (p == NULL)return false;
	LNode*cur=(LNode*)malloc(sizeof(LNode));
	cur->data = e;
	cur = p->next;
	p->next = cur;
	return ture;
}

指定结点的后插操作:在p结点后插入元素e

bool InsertNextNode(LNode* p, int e) {
	if (p == NULL)return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL)return false;//极少数情况下内存不够分配失败
	s->data = e;
	s = p->next;
	p->next = s;
	return true;
}

 指定结点的前插操作:此时会出现问题,单向链表并不知道前面是什么,所以要么传入头指针, 依次遍历,要么“偷天换日”。

bool InsertPriorNode(LNode* p, int e) {
	if (p == NULL)
		return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL)
		return false;
	s->next = p->next;
	p->next = s;//先把结点插入进去,然后交换数据
	s->data = p->data;
	p->data = e;
	return true;
}

 按位序删除(带虚拟头结点)

bool ListInsert(LinkList& L, int i, int &e) {
	if (i < 1)return false;
	LNode* p;//指针p用来扫描
	int j = 0;//记录指针p当前扫描到第几个结点
	p = L;//L指向头节点,头节点是第0个节点
	while (p != NULL && j < i - 1) {//循环找到第i-1个节点
		j++;
		p = p->next;
	}
	if (p == NULL)return false;
	LNode *q=p->next;
    e=q->data;
    p->next=q->next;
    free(q);
	return ture;
}

指定结点的删除

bool DeleteNode(LNode* p) {//这里的删除只是值改变了,实际的指针没变
	if (p == NULL)
		return false;
	LNode* q = p->next;
	p->data = p->next->data;
	p->next = q->next;
	free(q);
	return true;
}

但是,如果p是最后一个结点,那么我们操作p->next->data就会出现问题,此时要么重新遍历查找,要么就这样算了,考试时最多扣一分甚至不扣分。 

 2.3.3单链表的查找

按位查找

LNode* GetElem(LinkList L, int i) {
	int j = 0;
	LNode* p;
	p = L;
	if (i < 1)
		return NULL;
	if (i == 0) {
		return L;
	}
	while (p!= NULL && j < i) {
		j++;
		p = p->next;
	}
	return p;
}

按值查找

LNode* LocateElem(int val, LinkList& L) {
	LNode* p = L->next;
	while (p != NULL && p->data != val) {
		p = p->next;
	}
	return p;
}

2.3.4 单链表的建立

头插法建立单链表:

void HeadInsert (LNode*& L, int a[], int n) {
	L = (LNode*)malloc(sizeof(LNode));
	L->next = NULL;//初始化的工作不能忘记!
	LNode* s;
	for (int i = 0; i < n; i++) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = a[i];
		s->next = L->next;//头插法的关键步骤
		L->next = s;
	}
}

尾插法建立单链表:

void TailInsert(LNode*& L, int a[], int n) {
	L = (LNode*)malloc(sizeof(LNode));
	L->next = NULL;
	LNode* s,*r;
	r = L;//头结点不能随意移动
	for (int i = 0; i < n; i++) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = a[i];
		//尾插法的关键步骤
		r->next = s;
		r = s;//r是一个指向终点的指针,每次都要移动
	}
	r->next = NULL;
}

2.3.5 双链表

双链表的定义:

typedef struct DLNode{
	int data;
	DLNode* next,*prior;
};

双链表的插入操作:

双链表的插入操作容易出现错误,1、2两行代码必须出现在第四行代码之前,否则会缺失指针。

p结点是原有结点,s结点为要插入的结点。

s->next=p->next;
s->prior=p;
p->next->prior=s;
p->next=s;

双链表的删除操作:

q=p->next;//保留p后继结点的功能。
q->next->prior=p;
p->next=q->next;
free(q);

双链表的建立:尾插法

void TailInsert(DLNode*& L, int a[], int n) {
	DLNode* s, * r;
	L = (DLNode*)malloc(sizeof(DLNode));
	r = L;
	L->next = NULL;
	L->prior = NULL;
	for (int i = 0; i < n; i++) {
		s = (DLNode*)malloc(sizeof(DLNode));
		s->data = a[i];
		r->next = s;
		s->prior = r;
		r = s;
	}
	r->next = NULL;
}

2.3.6循环链表

循环单链表:循环单链表中最后的next指针指向L;判空条件:带头结点的循环单链表head=head->next;链表为空,不带头节点的循环单链表,head=NULL时为空。

有时我们只设置尾指针而不设置头指针,这是因为尾指针效率更高(头指针想要遍历到尾,需要O(N)复杂度,但是尾指针只需要tali->next即可到达头指针)。

循环双链表:尾结点的next指向头节点,所有的next形成闭环,所有的prior形成闭环。

判空条件:不带头结点:head=NULL;带头结点:此时双链表是没有空指针的,只需要检查head->next==NULL||head->prior==head;二者只要有一个等于head即可判空。

2.3.7静态链表

在一些不支持指针的语言如basic中会用数组来模拟链表,它实际上利用游标来模拟指针,这里的指针指的是结点的相对地址(又称数组下标)。与顺序表一样,静态链表也需要一开始分配起始的存储空间。

#define MaxSize 20
typedef struct {
    int data;
    int next;
}StaticLinkList[MaxSize];

2.4顺序表的常见考法

2.4.1 元素逆置

给定一个顺序表,如何将其中元素逆置?可以设置两个整形变量i,j一个指向左端,一个指向右端,不断交换二者对应的值,直到二者相遇。

void reverse(int a[],int left,int right){
    for(int i=left,int j=right;i==j;i++,j--){
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值