数据结构学习笔记(2线性表)

线性表

1.定义

线性表是具有相同数据类型(每个数据元素所占空间一样大)的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则一般表示为L=(a1,a2,…,ai,ai+1,…,an)。

Q:何时传入参数的引用”&“? A:对参数的修改结果需要”带回来“。

2.顺序表

(1)定义

用顺序存储的方式实现线性表。知道一个数据元素的大小,用sizeof(ElemType)。需要预分配大片连续空间。若分配空间过小,则之后不方便拓展容量;若分配空间过大,则浪费内存资源。

(2)静态分配
#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;
}
int main()
{
    SqList L; //声明一个顺序表
    InitList(L); //初始化顺序表
    //……
    return 0;
}
(3)动态分配
#define InitSize 10  //顺序表的初始长度
typedef struct
{
    ElemType *data;  //指示动态分配数组的指针
    int MaxSize;
    int length;
}SeqList;

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

malloc、free函数:malloc函数的参数指明要分配多大的连续内存空间。

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

#include<stdlib.h> //malloc、free函数的头文件
#include<stdio.h>
#define InitSize 10
typedef struct
{
	int *data; //指示动态分配数组的指针
	int MaxSize;
	int length;
}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((L.MaxSize + len) * sizeof(int));
	for (int i = 0;i < L.length;i++)
	{
		L.data[i] = p[i];  //将数据复制到新区域
	}
	L.MaxSize += len; //顺序表最大长度增加len
	free(p);
}
int main()
{
	SeqList L;  //声明一个顺序表
	InitList(L);  //初始化顺序表
	//往顺序表中随便插入几个元素
	IncreaseSize(L, 5);
	return 0;
}
(4)基本操作

主要包含创建、销毁、增加、删除、更改、查找等操作。对于销毁操作,静态分配的静态数组是由系统自动回收空间,对于动态分配(链表),需要依次删除各个结点(free操作)。

插入
#include<stdlib.h> //malloc、free函数的头文件
#include<stdio.h>
#define MaxSize 10
typedef struct
{
	int data[MaxSize];
	int length;
}SqList;
void ListInsert(SqList &L, int i, int e)
{
	for (int j = L.length;j >= i;j--)
		L.data[j] = L.data[j - 1];  //将第i个元素及以后的元素右移
	L.data[i - 1] = e;
	L.length++;
}
int main()
{
	SeqList L;  //声明一个顺序表
	InitList(L);  //初始化顺序表
	//插入元素
	IncreaseSize(L, 5);
	return 0;
}

ListInsert函数中,还应判断i的范围是否有效(将函数类型定义为Bool类型),即:

if(i<1||i>L.length+1) return false;if(L.length>=MaxSize) return false;

问题规模n=L.length,最好情况是新元素插入到表尾,不需要移动元素,最好时间复杂度O(1),最坏为O(n)。平均情况:

假设新元素插入到任何一个位置的概率相同,即i=1,2,3,…,length+1的概率都是p=1/(n+1);

i=1,循环n次;i=2,循环n-1次;i=3,循环n-2次;…;i=n+1,循环0次。

则平均循环次数=np+(n-1)p+…+1*p=(n(n+1)/2)*(1/n+1)=n/2,故平均时间复杂度为O(n)。

删除
bool ListDelete(SqList &L, int i, int &e)
{
	if (i<1 || i>L.length) return false;
	e = L.data[i - 1];
	for (int j = i;j < L.length;j++)  //将第i个位置后的元素前移
		L.data[j - 1] = L.data[j];
	L.length--;  //线性表长度减1
	return true;
}
int main()
{
	SeqList L;  //声明一个顺序表
	InitList(L);  //初始化顺序表
	int e = -1;
	if (ListDelete(L, 3, e))
		printf("已删除第三个元素,删除的元素值=%d\n", e);
	else printf("位序i不合法,删除失败\n");
	return 0;
}

时间复杂度为O(n)。

查找
按位查找

GetElem(L,i): 获取表L中第i个位置的元素的值,其时间复杂度为O(1)

按值查找

LocateElem(L,e):在表L中查找具有给定关键值的元素,其时间复杂度为O(n)

若表内元素有序,可在O(log2n)的时间内找到。

(5)顺序表的特点

随机访问,即可以在O(1)时间内找到第i个元素;

存储密度高,每个节点只存储数据元素;

拓展容量和插入、删除操作不方便。

3.单链表

(1)定义
typedef struct LNode
{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList; 

要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点:LNode *LLinkList L。(声明一个指针指向单链表第一个结点)

一般来说,若强调这是一个单链表,使用LinkList;若强调这是一个结点,使用LNode

(2)初始化
不带头结点的单链表
bool InitList(LinkList &L)
{
	L = NULL;  //空表,防止脏数据
	return true;
}
带头结点的单链表
bool InitList(LinkList &L)
{
	L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点
	if (L == NULL)
		return false;  //内存不足,分配失败
	L->next = NULL;
	return true;
}
(3)基本操作
插入
按位序插入和后插操作

头结点可以看作"第0个结点".

//不带头结点
bool ListInsert(LinkList &L, int i, ElemType e)
{
	if (i < 1)
		return false;
	LNode *p;
	int j = 0;  //当前p指向的是第几个结点
	p = L;		//L指向头结点,头结点是第0个结点(不存数据)
	while (p != NULL && j < i - 1)  //循环找到第i-1个结点
	{
		p = p->next;
		j++;
	}
    //后插操作
	if (p == NULL)    //i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;     //将结点s连到p之后
	return true;
}

如果不带头结点,则插入、删除第1个元素时,需更改头指针L。

前插操作

“偷天换日”:新节点s连到p之后,将p中元素复制到s中,再将p中元素进行覆盖,时间复杂度为O(1).

bool InsertPriorNode(LNode *p, ElemType 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连到p之后
	s->data = p->data;	//将p中的元素复制到s中
	p->data = e;		//p中元素覆盖为e
	return true;
}
删除
按位序删除
bool ListDelete(LinkList &L, int i, ElemType &e)
{
	if (i < 1) return false;
	LNode *p;
	int j = 0;
	p = L;
	while (p != NULL && j < n - 1)  //循环找到第n-1个结点
	{
		p = p->next;
		j++;
	}
	if (p == NULL) 
		return false;
	if (p->next == NULL)  //第i-1个结点之后已无其他结点
		return false;
	LNode *q = p->next;   //令q指向被删除的结点
	e = q->data;		  //用e返回元素的值
	p->next = q->next;	  //将*q结点从链中断开
	free(q);
	return true;
}
指定结点的删除
bool DeleteNode(LNode *p)
{
	if (p == NULL) return false;
	LNode *q = p->next;   //令q指向*p的后继结点
	p->data = p->next->data;
	p->next = q->next;
	free(q);
	return true;
}

【注】如果p是最后一个结点,则只能从表头开始找到p的前驱,时间复杂度是O(n)。

​ 单链表的局限性:无法逆向检索,有时候不太方便。

单链表的建立
尾插法

(1)初始化单链表;

(2)设置变量length记录链表长度;

(3)

while 循环
{
    每次取一个数据元素e;
	ListInsert(L,length+1,e)插到尾部;
	length++}
头插法

初始空链表后,需执行L->next=NULL

4.双链表

(1)初始化
typedef struct DNode
{
	ElemType data;
	struct DNode *prior, *next;
}DNode,*DLinklist;
bool InitDLinkList(DLinkList &L)
{
	L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
	if (L == NULL)
		return false;
	L->prior = NULL;	//头结点的prior永远指向NULL
	L->next = NULL;		//头结点之后暂时还没有结点
	return true;
}
(2)插入操作
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s)
{
    if(p==NULL||s==NULL)
        return false;
    if(p->next!=NULL)  //如果p结点有后继结点
        p->next->prior=s;
    s->prior=p;
    p->next=s;
    return true;
}
(3)删除操作
bool DeleteNextDNode(DNode *p)
{
    if(p==NULL) return false;
    DNode *q=p->next;		   //找到p的后继结点q
    if(q==NULL) return false;  //p没有后继
    p->next=q->next;
    if(q->next!=NULL) q->next->prior=p;
    free(q);
    return true;
}
(4)遍历

前向遍历/后向遍历,时间复杂度为O(n)。

5.循环链表

单链表表尾结点的next指针指向NULL,而循环单链表表尾结点的next指针指向头结点。

循环单/双链表从一个结点出发,可以找到其他任何一个结点。

6.静态链表

data表示数据元素,next表示游标

适用场景:不支持指针的语言;数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

(1)定义静态链表
#define MaxSize 10
struct Node
{
    ElemType data;
    int next;      //下一个元素的数组下标
}

main函数中struct Node a[MaxSize];,用数组a作为静态链表。

定义的另一种方式:

#define MaxSize 10    //静态链表的最大长度
typedef struct
{
    ElemType data;
    int next;
}SLinkList[MaxSize];

这种定义方式等价于:

#define MaxSize 10    //静态链表的最大长度
struct Node
{
    ElemType data;
    int next;
};
typedef struct Node SLinkList[MaxSize];

即,可用SLinkList定义”一个长度为MaxSizeNode型数组。

在主函数中,声明一个静态链表可用SLinkList a;语句。

(2)基本操作

初始化静态链表:把a[0]next设为-1。

查找:从头结点出发挨个往后遍历结点。

插入位序为i的结点:

【1】找到一个空的结点,存入数据元素;

注:判断结点是否为空,可在初始化过程中把空闲元素赋某个值;之后查找,若某结点值为-2,说明该结点时空闲的。

【2】从头结点出发找到位序为i-1的结点;

【3】修改新结点的next

【4】修改i-1号结点的next

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值