目录
首先,我们要明白数据结构的三要素——逻辑结构,物理结构,数据的运算。
在数据结构的每一章节都是围绕着数据结构三要素进行展开的,而此系列博客也是此种结构。
而数据结构每章的命名都是逻辑结构。逻辑结构是一种数学模型,可以便于人们脱离代码理解数据结构的本质。
逻辑结构
由上可知,线性表就是一种逻辑结构。那么就可以引出问题,线性表是一种什么样的逻辑结构呢?
线性表的定义:是具有相同数据类型的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在表尾的情况,即没有特殊情况,表中所有元素统一增删。