线性表基础

线性表(注意:大题!!!)

线性表的定义和基本操作

线性表的各个数据元素有先后顺序,数据类型相同,所占空间大小相等

第一段代码:&的作用

“带回去”

#include <iostream>
using namespace std;
void test(int x)
{
	x = 1024;
	cout << "test函数内部 x=" <<x;
	cout <<"\n";
}
void test2(int & x)						//x为引用型参数
{
	x = 1024;
	cout << "test函数内部 x=" <<x;
	cout <<"\n";
}
int main()
{
	int x=1;
	cout<<"调用test函数前x="<<x;
	cout <<"\n";
	test2(x);							//调用test2函数后,参数x的值会被函数中的操作修改
   	cout << "调用test函数后x="<<x;		//输出结果为1024
	cout <<"\n";
   	return 0;
}

线性表的基本操作

函数名作用详细描述
InitList(&L)初始化表构造一个空的线性表L,分配内存空间
DestoryList(&L)销毁操作销毁线性表,并释放线性表L所占用的内存空间
ListInsert(&L,i,e)插入操作在表L中的弟第i个位置上插入指定元素e
ListDelete(&L,i,&e)删除操作删除表L中第i个位置上的元素,并用e返回删除元素的值
LocateElem(L,e)按值查找在表L中查找具有给定关键字值的元素
GetElem(L,i)按位查找获取表中第i个位置的元素
Empty(L)判空操作若L为空表,则返回true,否则返回false
Length(L)求表长返回线性表L的长度,即L中数据元素的个数
PrintList(L)输出操作按前后顺序输出线性表L 的所有元素值

对数据的操作

创建

消毁

增加

删除

查找

修改

为什么要实现对数据结构的基本操作

便于别人方便使用

避免重复工作

顺序表的主要特点

1、随机访问(因其在物理内存中连续存放)

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

3、扩容不方便

4、插入和删除需要移动大量元素

顺序表的定义

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

顺序存储:逻辑上相邻的元素存储在物理位置上也相邻的存储单元中

如何知道数据元素的大小:

C语言:sizeof(ElemType),如sizeof(int)

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

数组长度一旦确定就不允许改变

初始化即给数组各个元素分配连续的存储空间,大小为MaxSize*sizeof(Elemtype)

定义数据元素结构体

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;
}
静态分配局限性:

顺序表大小一旦确定不可更改

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

#define IniteSize 10
typedef struct{
	ElemType *data;
	int MaxSize;
	int length;
}SeqList;

//初始化
void InitList(SeqList &L)
{
	L.data=(ElemType *)malloc(InitSize*sizeof(ElemType));
	L.length=0;
	L.MaxSize=InitSize;
}

//容量扩充
void IncreaseItem(SeqList &L,int len)
{
	int *p=L.data;
	L.data=(ElemType*)malloc((L.MaxSize+len)*sizeof(ElemType));
	for(int i=0;i<L.length;i++)
		L.data[i] = p[i];
	L.MaxSize = L.MaxSize + len;
	free(p);
}

动态分配扩充容量需要的时间长

顺序表的基本操作——插入

若在第i个位置插入某个元素,则第i个元素及其之后的元素都要向后移

bool ListInsert(SqList &L,int i,ElemType e)//i为位序,从1开始
{
	if(L.length >= MaxSize)
		return false;
	if(i<1 || i>L.length+1)
		return false;
	for(int j=L.length;j>=i;j--)
		L.data[j]=L.data[j-1];
	L.data[i-1]=e;
	L.length++;
	return true;
}
时间复杂度分析

最好时间复杂度:O(1),新元素插入到第 n+1个位置,不用挪动元素,即不用执行for循环

最坏时间复杂度:O(n),新元素插入到 第 1 个位置,需移动n个元素,需执行n次for 循环

平均时间复杂度:O(n)

平均时间复杂度计算方法:

i=1;循环n次

i=2;循环n-1次

i=3;循环n-2次

·

·

·

i=n+1;循环0次

平均循环次数:1/(n+1)(0+1+2+···+n)=n/2

顺序表的基本操作——删除

bool ListDelete(SqList &L,int i,ElemType &e)
{
	if(i<1 || i>L.length)
		return false;
	e = L.data[i-1];
	for(int j=i;j<L.length;j++)
		L.data[j-1]=L.data[j];
	L.length = L.length-1;
	return true;
}
时间复杂度分析

最好时间复杂度:O(1),删除最后一个元素,不用挪动元素,即不用执行for循环

最坏时间复杂度:O(n),删除 第 1 个元素,需移动n-1个元素,需执行n-1次for 循环

平均时间复杂度:O(n)

平均时间复杂度计算方法:

i=1;循环n-1次

i=2;循环n-2次

i=3;循环n-3次

·

·

·

i=n;循环0次

平均循环次数:1/n(0+1+2+···+n-1)=(n-1)/2

顺序表的基本操作——查找

按位查找
bool GetElem(SqList L,int i,ElemType &e)//动态分配的数组也可直接用该方式按位查找
{
	if(i<1 || i>L.length)
		return false;
	e = L.data[i-1];
	return true;
}

时间复杂度:O(1),顺序表随机存取特性,数据表在内存中所有元素顺序存储

按值查找
int LocateElem(SqList L,ElemType e)
{
	for(int i=0;i<L.length;i++)
		if(L.data[i] == e)
			return i+1;//返回位序
	return 0;
}

注意:结构体类型的比较要逐项比较结构体各个分量

时间复杂度

最好时间复杂度:O(1),目标元素在表头,循环1次

最坏时间复杂度:O(n),目标元素在表尾,循环n次

平均时间复杂度: O(n)

线性表的链式表示

带头结点的单链表

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

//初始化
bool InitList(LinkList &L)
{
	L=(LNode *)malloc(sizeof(LNode));
	if(L==NULL)
		return false;//内存不足,分配失败
	L->next=NULL;
	return true;
}

//判断没有头结点的单链表是否为空
bool Empty(LinkList L)
{
	return (L == NULL);
}

//判断有头结点的单链表是否为空
bool Empty(LinkList L)
{
	return (L->next == NULL);
}

单链表基本操作——插入

指定位置的插入操作
带头结点
bool InsertList(LinkList &L,int i,ElemType e)
{
	if(i<1)
		return false;
	LNode *p;//指针p指向当前扫描到的结点
	int j=0;//当前p指向哪一个节点
	p=L;//指向头结点,无数据
	while(j<i-1 && p!=NULL)
	{
		p = p->next;
		j++;
	}
	if(p == NULL)
		return false;
	LNode *s=(LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

时间复杂度:最好,O(1);最坏,O(n);平均:O(n)

不带头结点
bool InsertList(LinkList &L,int i,ElemType e)
{
	if(i<1)
		return false;
	if(i==1)//插入第一个元素时需修改头指针的指向
	{
		LNode *s=(LNode *)malloc(sizeof(LNode));
		s->data=e;
		s->next=L->next;
		L=s;
		return true;
	}
	LNode *p;
	int j=1;
	p=L;
	while(p!=NULL && j<i-1)
	{
		p=p->next;
		j++;
	}
	if(p == NULL) return false;
	LNode *s=(LNode *)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next;
	p->next=s;
	return true;
}

时间复杂度:最好,O(1);最坏,O(n);平均:O(n)

指定节点的插入操作
前插

在p节点之前插入元素e

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->data=p->data;
	p->data=e;
	return true;
}

时间复杂度:O(1)

在p结点前插入一个结点s

bool InsertPriorNode(LNode *p, LNode *s)
{
	if(p==NULL || s==NULL) return false;
	s->next=p->next;
	p->next=s;
	temp = s->data;
	s->data=p->data;
	p->data=temp;
}

时间复杂度:O(1)

后插
bool InsertNextNode(LNode *p,ElemType e)
{
	if(p== NULL) return false;
	LNode *s=(LNode *)malloc(sizeof(LNode));
	if(s==NULL) return false;
	s->data=e;
	s->next=p->next;
	p->next=s;
	return true;
}

时间复杂度:O(1)

单链表的建立

尾插法

定义一个指向尾结点的指针

//带头结点
LinkList List_TailInsert(LinkList &L)
{
	int x;//ElemType为整型
	L = (LNode *)malloc(sizeof(LNode));
	L->next=NULL;			//可以有,可以没有
	LNode *s,*r=L;			//r为表尾指针,L为表头指针,s为指向新插入元素的指针
	scanf("%d",&x);
	while(x!=1000)
	{
		s=(LNode *)malloc(sizeof(LNode));
		s->data=x;
		r->next=s;
		r=s;
		scanf("%d",&x);
	}
	r->next=NULL;
	return L;
}

//不带头结点
LinkList List_TailInsert(LinkList &L)
{
	int x;
	L = (LNode *)malloc(sizeof(LNode));
	L=NULL;//初始化
	LNode *s,*r=L;
	scanf("%d",&x);
	while(x!=1000)
	{
		s=(LNode *)malloc(sizeof(LNode));
		s->data=x;
		if(L==NULL)
		{
			L=s;
			r=L;
		}
		else{
			r->next=s;
			r=s;
		}
		scanf("%d",&x);
	}
	r->next=NULL;
	return L;
}

时间复杂度:O(n)

头插法(链表逆置)
//带头结点
LinkList List_HeadInsert(LinkList &L)
{
	int x;
	L = (LNode *)malloc(sizeof(LNode));
	L->next=NULL;		//一定要有!!!!!
	LNode *s;
	scanf("%d",&x);
	while(x!=1000)
	{
		s=(LNode *)malloc(sizeof(LNode));
        s->data=x;
		s->next=L->next;
		L->next=s;
		scanf("%d",&x);
	}
	return L;
}

//不带头结点
LinkList List_HeadInsert(LinkList &L)
{
	int x;
	L= (LNode *)malloc(sizeof(LNode));
	L=NULL;
	LNode *s;
	scanf("%d",&x);
	while(x!=1000)
	{
		s=(LNode *)malloc(sizeof(LNode));
        s->data=x;
		s->next=L;
		L=s;
		scanf("%d",&x);
	}
	return L;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndLk5gs8-1635472455111)(E:\桌面\新建文件夹\头插法.PNG)]

和后插操作类似

实现链表逆置

利用头结点插入实现链表逆置(带头结点)

//自己写的,有待商榷,还未测试
LinkList List_Reverse(LinkList &L,LinkList LastL)
{
	L= (LNode *)malloc(sizeof(LNode));
	L->next=NULL;
	LNode *p=LastL;
	LNode  *s;
	while(p->next!=NULL)
	{
		s=(LNode *)malloc(sizeof(LNode));
		p=p->next;
		s->data=p->data;
		s->next=L->next;
		L->next=s;
		free(p);
	}
	return L;
}

网上答案——头插法

void converse(LinkList *head)
{
	LinkList *p,*q;
	p=head->next;
	head->next=NULL;
	while(p)
	{
		q=p;
		p-p->next;
		q->next=head->next;
		head->next=q;
	}
}

网上答案——递归法

算法思想:先假定有一个函数,可以将以head为头结点的单链表逆序,并返回新的头结点。利用这个函数对问题进行求解;将链表分为当前头结点和其余部分,递归的过程就是,先将表头结点从链表中拆出来,然后对其余部分进行逆序,最后将当前的表头结点链接到逆序列表的尾部,递归的终止条件就是链表只剩一个结点时,直接返回该结点

/*
dfs深度遍历这个单链表
在递归的时候,不作处理
在回溯时,标记回溯开始的位置(新的头结点),将节点和节点的下一节点逆置,将新的头结点传递给上一层递归,
接下来逆置上一个节点和上一节点的下一个节点(逆置已逆置链表的尾节点),并传递标记的头结点,直到回溯完成
我们就得到了逆转的单链表和它的头结点
dfs(当前状态)
{
    if(边界状态)   //head==NULL说明链表为空,不用处理, 从头到尾我们需要处理的最后一个节点是倒数第二个节点,将它和倒数第一逆置,因此,遍历到倒是第一个节点就是边界条件
    {
       记录       //标记头结点位置并传递(这里选用返回值,还可以通过全局变量)给上一层递归
    }
    dfs(下一状态) 
    //回溯部分    //节点和下一节点(逆置这已逆置链表的尾结点和),将新的头结点传递给上一层递归
}
*/
ListNode *reverse(ListNode *head)
{
    if(head==NULL || head->next ==NULL)
        return head;
 
    /*递归*/
    ListNode* headOfReverse = reverse(head->next);
    // cout<<head->next<<" "<<headOfReverse<<endl;
 
    /*回溯:将当前表头结点链接到逆序链表的尾部*/
    head->next->next = head;
    head->next = NULL;
    return headOfReverse;
}

单链表基本操作——删除

删除第i位置的结点,并返回该结点的值;先查找第i-1位置的结点(带头结点)

bool ListDelete(LinkList &L,int i;ElemType &e)
{
	if(i<1) return false;
	LNode *p;
	int j=0;
	p=L;
	while(p!=NULL&&j<i-1)
	{
		p=p->next;
		j++;
	}
	if(p==NULL) return false;
	if(p->next == NULL) return false;
	LNode *q=p->next;
	e=q->data;
	p->next=q->next;
	free(q);
	return true;
}

时间复杂度:最好,O(1);最坏,O(n);平均:O(n)

单链表基本操作——查找

按位查找(带头结点)

获取表L中第i个位置的元素结点

//按位查找,返回第i个元素
LNode * GetElem(LinkList L,int i)
{
	if(i<0) return NULL;
	LNode *p;
	int j=0;
	p=L;
	while(p!=NULL && j<i)
	{
		p=p->next;
		j++;
	}
	return p;
}

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

平均时间复杂度:O(n)

按值查找(带头结点)

在表L 中,找到数据域==e的结点

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

平均时间复杂度:O(n)

单链表基本操作——表长

int GetLinkListLen(LinkList L)
{
	int len=0;
	LNode *p=L->next;
	while(p!=NULL)
	{
		len++;
		p=p->next;
	}
	return len;
}

int length(LinkList L)
{
	int len=0;
	LNode *p=L;
	while(p->next!=NULL)
	{
		p=p->next;
		len++;
	}
	return len;
}

平均时间复杂度:O(n)

双链表的基本操作——创建

双链表的按值查找和按位查找与单链表操作相同

双链表的插入和删除与单链表操作不同

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,*DLinklist;

//初始化双链表
bool InitDLinklist(DLinklist &L)
{
	L=(DNode *)malloc(DNode);
	if(L==NULL) return false;
	L->prior=NULL;
	L->next=NULL;
	return true;
}

//判断双链表是否为空
bool Empty(DLinklist L){
	if(L->next == NULL)
		return true;
	else
		return false;
}

双链表的基本操作——插入

//后插
bool InsertNextNode(DNode *p,DNode *s)
{
	if(p==NULL || s==NULL)
		return false;
	s->next=p->next;
	if(p->next != NULL)		//若p结点有后继结点
		p->next->prior=s;
	s->prior=p;
	p->next=s;
	return true;
}

//前插
//找到给定节点的前驱节点,对该前驱节点实现后插操作
bool InsertPriorDNode(DNode *p,DNode *s)
{
	if(p==NULL || s==NULL)
		return false;
	DNode *q=p->prior;
	InsertNextNode(q,s);
}

双链表的基本操作——删除

//删除p的后继节点s
bool DeleteDNode(DNode *p)
{
	if(p== NULL)
		return false;
	DNode *q=p->next;
	if(q==NULL) return false;
	p->next=q->next;
	if(q->next!=NULL)
		q->next->prior=p;
	free(q);
	return true;
}

//循环释放各个数据结点
void DestroyList(DLinklist &L)
{
	while(L->next != NULL)
		DeleteDNode(L);
	free(L);
	L=NULL;
}

循环单链表——创建

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

//初始化一个循环单链表
bool InitList(LinkList &L)
{
    L = (LNode *)malloc(sizeof(LNode));
    if(L==NULL) return false;//内存不足,分配失败
    L->next=L;//L的头结点next指向头结点
    return true;
}

//判断循环单链表是否为空
bool Empty(LinkList L)
{
    if(L->next==L)
        return true;
    else
        return false;
}

//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p)
{
    if(p->next == L)
        return true;
    else
        return false;
}

循环双链表——创建

头结点的prior指针指向尾结点;尾结点的next指针指向头结点

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->next=L;
    L->prior=L;
    return true;
}

//判断循环双链表是否为空
bool Empty(DLinklist L)
{
    if(L->next==L)
        return true;
    else
        return false;
}

//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinklist L,DNode *p)
{
    if(p->next==L)
        return true;
    else
        return false;
}

循环双链表——插入

//后插
bool InsertNextNode(DNode *p,DNode *s)
{
	if(p==NULL || s==NULL)
		return false;
	s->next=p->next;
	p->next->prior=s;
	s->prior=p;
	p->next=s;
	return true;
}

循环双链表——删除

//删除p的后继节点q
bool DeleteDNode(DNode *p)
{
	if(p== NULL)
		return false;
	DNode *q=p->next;
	if(q==NULL) return false;
	p->next=q->next;
	q->next->prior=p;
	free(q);
	return true;
}

静态链表的定义

第一种

#define MaxSize 10
typedef struct{
	ElemType data;
	int next;
}SLinkList[MaxSize];
//SLinkList b相当于定义了一个长度为MaxSize的Node型数组
void main()
{
	SLinkList b;
}

第二种

#define MaxSize 10
struct Node{
    ElemData data;
    int next;
};
void main(){
    struct Node a[MaxSize];
}

静态链表基本操作

初始化:

1、把a[0]的next设为-1

2、把其他结点的next设为一个特殊值用来表示结点空闲,如-2

插入位序为i的结点:

1、找到一个空结点(若结点next值为-2,则该结点为空),存入数据元素

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

3、修改新结点的next

4、修改i-1号结点的next

删除某个结点:

1、从头结点除法找到前驱结点

2、修改前驱节点的游标

——插入

//后插
bool InsertNextNode(DNode *p,DNode *s)
{
	if(p==NULL || s==NULL)
		return false;
	s->next=p->next;
	p->next->prior=s;
	s->prior=p;
	p->next=s;
	return true;
}

循环双链表——删除

//删除p的后继节点q
bool DeleteDNode(DNode *p)
{
	if(p== NULL)
		return false;
	DNode *q=p->next;
	if(q==NULL) return false;
	p->next=q->next;
	q->next->prior=p;
	free(q);
	return true;
}

静态链表的定义

第一种

#define MaxSize 10
typedef struct{
	ElemType data;
	int next;
}SLinkList[MaxSize];
//SLinkList b相当于定义了一个长度为MaxSize的Node型数组
void main()
{
	SLinkList b;
}

第二种

#define MaxSize 10
struct Node{
    ElemData data;
    int next;
};
void main(){
    struct Node a[MaxSize];
}

静态链表基本操作

初始化:

1、把a[0]的next设为-1

2、把其他结点的next设为一个特殊值用来表示结点空闲,如-2

插入位序为i的结点:

1、找到一个空结点(若结点next值为-2,则该结点为空),存入数据元素

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

3、修改新结点的next

4、修改i-1号结点的next

删除某个结点:

1、从头结点除法找到前驱结点

2、修改前驱节点的游标

3、被删除结点next设为-2

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值