数据结构——线性表

目录

逻辑结构

数据的运算

物理结构

一、顺序表

二、单链表

三、双链表

四、循环链表

首先,我们要明白数据结构的三要素——逻辑结构,物理结构,数据的运算。

在数据结构的每一章节都是围绕着数据结构三要素进行展开的,而此系列博客也是此种结构。

而数据结构每章的命名都是逻辑结构。逻辑结构是一种数学模型,可以便于人们脱离代码理解数据结构的本质。

逻辑结构

由上可知,线性表就是一种逻辑结构。那么就可以引出问题,线性表是一种什么样的逻辑结构呢?

线性表的定义:是具有相同数据类型的n个数据元素的有限序列。

需要注意的是:线性表是有次序的,除了表头和表尾的其他元素都需要有一个前驱和一个后继,线性表是一种一对一的顺序结构。

例图:

数据的运算

由于线性表有不止一种的存储结构,所以先统一的介绍线性表的操作。

初始化操作InitList(&L):创建一个新线性表L。

销毁操作DestroyList(&L):销毁一个线性表L。

插入操作ListInsert(&L,i,e):在线性表L的某个位置插入某个元素,i为次序(严格来说可为位置下标为次序减一),e为插入的元素。

删除操作ListDelete(&L,i,&e):在表L中删除i位置的值为e的元素。

按值查找LocateElem(L,e):在表L中查找值为e的元素。

按位查找GetElem(L,i):在表L中查找i位置的元素。

求表长Length(L):表L中数据元素的个数。

打印表PrintList(L):打印表L。

判空操作Empty(L):判断表L是否为空表。

带取地址符是因为需要在操作内对该参数进行修改,在c++中若操作函数中参数不带取地址符,修改过后的数据没法再次带回到主函数中,即只是在操作内修改。

物理结构

物理结构是

一、顺序表

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

特点:逻辑上相邻的元素存储在硬盘相邻的空间里。

首先定义一个顺序表

 (1)静态分配

#define MaxSize 10//线性表的最大长度
typedef struct
{
	ElemType data[MaxSize];
	int length;
}List;

 (2)动态分配

#define MaxSize 10//线性表的最大长度
typedef struct
{
	ElemType *data;
	int MaxSize;
	int length;
}List;

  InitList(&L) 

  初始化操作

#include <stdio.h>
#define MaxSize 10//线性表的最大长度
typedef struct
{
	ElemType data[MaxSize];
	int length;
}List;
void InitList(List &L)
{
	L.length=0;
}
int main
{
	List L;
	InitList(L);
}

初始化操作

#include <stdlib.h>
#include <stdio.h>
#define MaxSize 10//线性表的最大长度
typedef struct
{
	ElemType *data;
	int maxSize;
	int length;
}List;//表的类型名
void InitList(List &L)
{
	L.data=(ElemType *)malloc(MaxSize*sizeof(ElemType));
	L.maxSize=MaxSize;
	L.length=0;
}
int main
{
	List L;
	InitList(L);
}

两种定义方式的区别在于:静态分配的空间是固定的,而动态分配的空间是可以增加的。

可以用函数CreaseSize(&L,len)来增加,代码如下:

void CreaseSize(List &L,int len)
{
	ElemType *p;
	p=L.data;
	L.data=(ElemType *)malloc((MaxSize+len)*sizeof(ElemType));
	for(int i=0;i<MaxSize;i++)
	{
		L.data[i]=p[i];
	}
	L.maxSize=MaxSize+len;
	free(p);
}

ListInsert(&L,i,e)(其余操作不用考虑分配方式是动态还是静态)

bool ListInsert(List &L,int i,ElemType e)//此处认为i是下标位置而不是逻辑次序
{
	if(i<0||i>L.length+1)//隔空插入会影响代码的健壮性
	{
		return false;
	}
    if(i>L.maxSize-1)//data最大下标应该为L.maxSize-1
	{
		return false;
	}
	L.length+=1;
	for(int j=L.length-1;j>i;j--)
	{
		L.data[j]=L.data[j-1];
	}
	L.data[i]=e;
}

时间复杂度=O(n)

最好时间复杂度为往表尾插,时间复杂度为O(1)。

最坏时间复杂度为往表头插入,时间复杂度为O(n)。

平均复杂度为,向每个位置插入的概率为p=1/(n+1)。

时间复杂度为(n*n+1)/2*p=n/2。

ListDelete(&L,i,&e)

bool ListDelete(List &L,int i,ElemType &e)//此处认为i是下标位置而不是逻辑次序
{
	if(i<0||i>L.length-1)
	{
		return false;
	}
	for(int j=i;j<L.length-1;j++)
	{
		L.data[j]=L.data[j+1];
	}
	L.length--;
	return true;
}

时间复杂度为O(n)

LocateElem(L,e)

int LocateElem(List L,ElemType e)
{
	for(int i=0;i<L.length;i++)
	{
		if(L.data[i]==e)
		{
			return i;
		}
	}
	return -1;
}

时间复杂度O(n)

GetElem(L,i)

ElemType GetElem(List L,int i)//此处认为i是下标位置而不是逻辑次序
{
	if(i<0||i>L.length-1)
	{
		return;
	}
	return L.data[i];
}

时间复杂度为O(1)

二、单链表

单链表:用结点来存储数据,一个结点中存储两个变量,一个是数据一个是指向下一个结点的指针变量。

特点:不用开辟顺序存储空间。

结点定义为:

typedef struct LNode//单链表的数据用int类型,数据类型可自定义但博主为了测试选用int
{
	int data;
	struct LNode* next;
}LNode,*LinkList;

InitList(&L) 

首先定义一个单链表,有两种定义方式。

(1)不带头结点

bool InitList(LinkList &L)
{
	L= NULL;
	return true;
}

(2)带头结点

bool InitListt(LinkList &L)
{
	L=(LNode *)malloc(sizeof(LNode));//分配一个结点使L指针指向它的起始地址
	if(L==NULL)
	{
		return false;
	}
	L->next=NULL;
	return true;
}

ListInsert(&L,i,e)

(带头结点)

bool ListInsert(LinkList &L,int i,int e)
{
	if(i<1)
	{
		return false;
	}
	LNode *p=L;
	int j=0;
	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;
}

(不带头结点)

bool ListInsert(LinkList &L,int i,int e)
{
	if(i==1)
	{
		LNode *s=(LNode *)malloc(sizeof(LNode));
		s->data=e;
		L=s;
		s->next=NULL;
        return true;
	}
	if(i<1)
	{
		return false;
	}
	LNode *p=L;
	int j=0;
	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;
}

指定结点的后插操作

    if(p==NULL)
	{
		return false;
	}
	LNode *s=(LNode *)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next;
	p->next=s;
	return true;

指定结点的前插操作

bool ProInsert(LNode *p,int e)
{
	if(p==NULL)
	{
		return false;
	}
	LNode *s=(LNode *)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next;
	p->next=s;
	s->data=p->data;
	p->data=e;
}

ListDelete(&L,i,&e)

带头结点的

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

不带头结点的

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

指定结点的删除

GetElem(L,i)

带头结点

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

不带头结点

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

LocateElem(L,e)

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

单链表的建立

尾插法和头插法

尾插法(带头结点的跟这个类似)

头插法(不带头结点的跟这个类似)

三、双链表

 

双链表的定义结构为

typedef struct LNode
{
    int data;
    struct LNode *next,*prior;
}LNode,*LinkList;

InitList(&L) 

bool InitList(LinkList &L)//带头结点
{
	L=(LNode *)malloc(sizeof(LNode));
	if(L==NULL)
		return false;
	L->next=NULL;
	L->prior=NULL;
	return true;
}

ListInsert(&L,i,e)

bool ListInsert(LinkList &L,int i,int e)
{
	if(i<1)
		return false;
	LNode *p=L->next;
	int j=0;
	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;
	if(p->next!=NULL)
	{
		p->next->prior=s;
	}
	s->prior=p;
	p->next=s;
}

ListDelete(&L,i,&e)

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

查找跟单链表类似,不同的是双链表可以逆序。

四、循环链表

循环单链表

 

typedef struct LNode//单链表的数据用int类型,数据类型可自定义但博主为了测试选用int
{
	int data;
	struct LNode* next;
}LNode,*LinkList;

循环双链表

 

typedef struct LNode
{
    int data;
    struct LNode *next,*prior;
}LNode,*LinkList;

InitList(&L) 

  初始化操作

循环单链表

bool InitList(LinkList &L)//带头结点
{
	L=(LNode *)malloc(sizeof(LNode));
	if(L==NULL)
	{
		return false;
	}
	L->next=L;
} 

循环双链表

bool InitList(LinkList &L)//带头结点
{
	L=(LNode *)malloc(sizeof(LNode));
	if(L==NULL)
	{
		return false;
	}
	L->next=L;
	L->prior=L;
} 

增删操作与单链表和双链表一样,唯一不同的是不用考虑i在表尾的情况,即没有特殊情况,表中所有元素统一增删。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值