1 线性表
线性表是由n个数据元素组成的有限序列,其中元素的个数n为线性表的长度。当n=0时称为空表。通常将非空线性表表示为L=(a1,a2,a3,a4…,an)
1.1线性表的特点
线性表中各元素具有相同的数据类型;
i是ai在线性表中的位序;
对于一个非空线性表,有且只有一个开始节点a1和一个终端节点an。除此之外,其他的所有节点都具有一个直接前驱和直接后继。
2 顺序表
线性表的元素按照逻辑顺序依次存放在一组地址连续的存储单元里。用这种存储方法的线性表称为顺序表。
2.1静态分配存储单元
#define maxsize 1024
typedef int datatype;
typedef struct{
datatype data[maxsize];//采用一维数组存储线性表
int last;//顺序表当前的长度
}Sqlist;
2.1.1初始化顺序表
#define maxsize 1024
typedef int datatype;
typedef struct{
datatype data[maxsize];//采用一维数组存储线性表
int last;//顺序表当前的长度
}Sqlist;
void Initlist(Sqlist &L){
for(i=0;i<maxsize;i++){
L.data[i]=0;//所有数据元素初始化为0
}
L.last=0;// 链表长度初始化为0
}
int main(){
SqList L; //声明一个顺序表
InitList(L); //初始化顺序表
return 0;
}
2.2动态分配存储单元
sequenlist*InitList()
{
sequenlist*L=(sequenlist*)malloc(sizeof(sequenlist));//分配顺序表的动态存储空间
L->last=0;//表长初始化为0
return L;
}
通过函数返回值将结果带回到主函数
void InitList(sequenlist**L)
{
L*=(sequenlist*)malloc(sizeof(sequenlist));
L->=0;
}
通过二级指针将L*带回主函数
2.3 顺序表的特点
1 可以随机访问;
2 存储密度高;
3 顺序存取元素;
4 缺点是插入和删除时效率比较低,需要移动大量的元素。
2.4顺序表的插入
#define maxsize 1024
typedef int datatype;
typedef struct{
datatype data[maxsize];//采用一维数组存储线性表
int last;//顺序表当前的长度
}Sqlist;
//在顺序表的第i个元素的位置插入一个新的结点x
int Insert(Sqlist *L,int i,datatype x)
{
if(L->last==maxsize){printf("表满");return 0;
}
else if(i<1||i>L->last){paintf("非法插入位置");return 0;
}
else {
for(j=L->last;j>=i;j--)
{
L->data[j]=L->data[j-1]; //将第i个元素及之后的元素后移
}
L->data[i-1]=x;//在位置i处放入e,注意位序、数组下标的关系
L->last++;
}
return 1;
}
int main()
{
Sqlist L;
InitList L;
scarnf("%d",&last);
for(k=0;k<last;k++)
{
scarnf("%d",data[k]);
}
Insert(L,3,3);
return 0;
}
2.4.1顺序表插入的时间复杂度
for(j=L->last;j>=i;j--)
{
L->data[j]=L->data[j-1]; //将第i个元素及之后的元素后移
}
最好的情况是新元素插入到表尾,不需要移动元素,时间复杂度为O(1);
最坏的情况是新元素插入到表头,需要将n个元素全部后移,时间复杂度为O(n)
一般情况时新元素插入到随机位置,时间复杂度为O(n);
2.5顺序表的删除
#define maxsize 1024
typedef int datatype;
typedef struct{
datatype data[maxsize];//采用一维数组存储线性表
int last;//顺序表当前的长度
}Sqlist;
//在顺序表的第i个元素的位置插入一个新的结点x
int delete(Sqlist *L,int i)
{
if(i<1||i>L->last){paintf("非法删除位置");return 0;
}
else {
for(j=i;j<L->last;j++)
{
L->data[j-1]=L->data[j]; //节点前移
}
L->last--;
}
return 1;
}
int main()
{
Sqlist L;
InitList L;
scarnf("%d",&last);
for(k=0;k<last;k++)
{
scarnf("%d",data[k]);
}
delete(L,3);
return 0;
}
2.5.1顺序表删除的时间复杂度
for(j=i;j<L->last;j++)
{
L->data[j-1]=L->data[j]; //节点前移
}
最好的情况是删除表尾,时间复杂度为O(1);
最坏的情况是删除表头,时间复杂度为O(n)
一般情况时时间复杂度为O(n);
3 单链表
优点:不需要连续的存储空间;
缺点:不可以随机存取,需要浪费空间存储指针。
3.1 单链表结构类型定义
typedef struct node
{
ElemType data;
struct node * next;
} linklist;
linklist*head,*p;
3.2 初始化不带头结点的单链表
linklist*CreatList()
{
linklist*head,*p,*r;
char ch;
head=NULL;
r=NULL;//建立空表,尾指针为空
while((ch=getchar())!='\n')
{
p=(linklist*)malloc(sizeof(linklist));
p->data=ch;// 输入的字符赋值给新节点的数据域
if(head==NULL)
head=p;//将新节点插入到空表
else
r->next=p;//新节点插入到非空表的表尾
r=p;//指针r指向表尾
}
if(r!=NULL)
r->next=NULL;//对于非空表,将终端节点的指针域 置空
return head;
}
3.3 判断不带头节点的单链表是否为空
//判断单链表是否为空
bool Empty(LinkList L){
return (L==NULL);
}
bool是布尔型变量,作用是逻辑型变量的定义符。bool取false和true,是0和1的区别,false代表0,true可以代表很多,不一定是1.
Empty是检查一个变量是否为空
3.4 初始化带头节点的单链表
linklist*CreatList()//头插法
{
linklist*head,*p;
char ch;
head=(linklist*)malloc(sizeof(linklist));
head->next=NULL;
while((ch=getchar())!='\n')
{
p=(linklist*)malloc(sizeof(linklist));
p->data=ch;
p->next=head->next;
head->next=p;
}
return head;
}
3.5 判断不带头节点的单链表是否为空
//判断单链表是否为空
//判断单链表是否为空(带头结点)
bool Empty(LinkList L){
if(L->next == NULL){
return true;
}else{
return false;
}
}
3.6 单链表的插入
3.6.1带头结点的单链表的插入
void Insert(linklist*L,datatype x,int i) //L指向具有头结点的单链表
{
linklist*p,*s;
p=Get(L,i-1) ;//调用get算法,查找到第i-1个节点*p
if(p==NULL)printf("查找不到第一个节点");
else
{
s=(linklist*)malloc(sizeof(linklist));
s->data=x;
s->next=p->next;//新节点的指针域指向节点ai
p->next=s;//节点ai的指针域指向新的节点
}
}
3.6.2不带头结点的单链表的插入
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
{
return false;
}
if(i==1) //插入第1个结点的操作与其他结点操作不同
{
LNode *s =(LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L->next;
L = s; //头指针指向新结点
return true;
}
LNode *p; //指针p指向当前扫描到的结点
int j=1; //当前p指向的是第几个结点
p = L; //p指向第1个结点(注意:不是头结点)
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指针
s->next = p->next;
p->next = s; //将p指向结点的next指针指向s
return true; //插入成功
}
}
3.7 指定节点的后插操作
//后插操作:在p结点之后插入元素e
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;
}
3.8 指定节点的前插操作
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;
}
4双向链表
4.1 双向链表结构体类型定义
typedef struct dnode
{
datatype data;
struct dnode*prior,*next;
}linklist;
dlinklist*h
4.2 双向链表的后插
void DinsertAfter(dlinklist*p,datatype x)
{//在带头结点的非空双向链表中,将值为x的新节点插入到*p之后
dlinklist*s=(dlinklist)malloc(sizeof(dlinklist));
s->data=x;
s->prior=p;
s->next=p->next;
p->next=s;
}
4.3双向链表的前插
void DinsertBefore(dlinklist*p,datatype x)
{
dlinklist*s=(dlinklist*)malloc(sizeof(dlinklist));
s->data=x;
s->prior=p->prior;
s->next=p;
p->prior-next=s;
p->prior=s;
}
5 循环链表
循环链表是一种首尾相接的链式存储结构。通过表尾指针可以一步得到表头指针。
6 顺序表和链表的比较
线性表 | 优点 | 缺点 |
---|---|---|
顺序表(顺序存储) | 支持随机存取、存储密度高 | 大片连续空间分配不方便,改变容量不方便 |
链表(链式存储) | 离散的小空间分配方便,改变容量方便 | 不可随机存取,存储密度低 |
*表长难以预估、经常要增加/删除元素——链表
*表长可预估、查询(搜索)操作较多——顺序表