数据结构与算法系列-线性表-线式存储结构、链式存储结构

线性表的定义:

线性表是由N个数据元素组成的有限序列,N为数据元素的个数。

线性表是一种非常典型的线性结构


举例:

26个英文字母(A,B,C……Z)就是一个线性表

线性表的顺序存储结构称为顺序表,顺序表是将线性表中的数据元素按其逻辑顺序依次存放在内存中一组地址连续的存储单元中,即把线性表的相邻元素存在相邻的内存单元中。

 

顺序表的特点是逻辑上相邻的两个元素在物理位置也相邻,可以用一个简单的公式计算出某一元素的存放位置。因此为线性表的存取是很容易的。

 

线性表的缺点:

对线性表进行插入或者删除的时候,需要移动大量元素,消耗时间较多,另外顺序表是用数组来存放线性表中各个元素的,线性表中的最大长度难确,必须按照线性表的最大可能长度来分配空间,若是线性表长度变化较大时,则使存储空间不能得到充分利用,如果分配较小,有可能导致溢出。

线性表的描述

# define MAXLEN 100  //maxlen 要大于实际线性表的长度
typedef int elementtype;
typedef struct{
	elementtype s[MAXLEN]; //定义线性表中的元素,MAXLEN为线性表的最大容量
	int len;			//定义线性表的表长
}SqList;

线性表的插入运算。(图片来自网络)


在具有n个线性表的第i(1<=i<=n)个元素之前插入一个新元素

由于顺序表中的元素在机器内是连续存放的,要在第i个元素之前插入一个新元素,就必须把第n个到第i个元素依次向后以后一个位置。

#include <stdio.h>
# define MAXLEN 100  //maxlen 要大于实际线性表的长度
typedef int elementtype;
typedef struct{
	elementtype s[MAXLEN]; //定义线性表中的元素,MAXLEN为线性表的最大容量
	int len;			//定义线性表的表长
}SqList;

/*在顺序表 *sql 的第i个元素之前插入一个新的元素x */
int insertsqlist(int i,elementtype x,SqList *sql){
	int j;
	if((i<1)||(i>sql->len)){
		return 0;
	}else{
		for(j = sql->len;j>=i;j--){
			sql->s[j+1] = sql ->s[j]; //向后移动数据 腾出要插入的空位
		}
		sql ->s[j+1] = x;		//修正插入位置为 j+1,将新元素插入到s[j+1]位置
		(sql -> len)++;			//表长加1
		return 1;
	}
}
main(){
	int b =3,c,k;
	elementtype d = 9;
	SqList a = {0,1,2,3,4,5,6,7,8};
	a.len = 8;
	for(k=1;k<a.len;k++){
		printf("% 3d",a.s[k]);
	}
	printf("\n");
	c = insertsqlist(b,d,&a);
	if(c==0){
		printf("error");
	}else{
		for(k=1;k<a.len;k++){
		printf("% 3d",a.s[k]);
		}
	}
	printf("\n");
}


插入一个新元素需要移动的平均次数为 n/2


线性表的删除运算

#include<stdio.h>
#define MAXLEN 100   /*MAXLEN 要大于实际线性表的长度*/
typedef int elementtype;
typedef struct{				
	elementtype s[MAXLEN];
	int len;
}SqList;

int delsqllist(int i,SqList *sql){
	int j;
	if((i<1)||(i>sql->len)){
		return(0);
	}else{
		for(j=i+1;j<=sql->len;j++){
			sql ->s[j-1] = sql ->s[j]; /*向前移动数据,覆盖前一数据*/
		}
		(sql->len)--;
		return 1;
	}
}

main(){
	int b=4,c,k;
	SqList a = {0,1,2,3,4,5,6,7,8};
	/*赋线性表各元素初值,为之前概念描述一致,a.s[0]闲置不用*/
	a.len = 8 ;
	for(k=1;k<a.len;k++){
		printf("%3d",a.s[k]);  /*删除之前*/
	}
	printf("\n");
	c= delsqllist(b,&a);
	if(c==0){
		printf("error \n");
	}	
	else{
		for(k=1;k<a.len;k++){
			printf("%3d",a.s[k]); /*删除之后*/ 
		}

	}
	printf("\n");
}

删除一个元素需要移动的平均次数为 (n-1)/2

当线性表中的元素很多时,算法的效率很低。若表长n,则插入和删除的时间复杂度为O(n)


因为线性表的缺点  我们可以采用另外一种存储方式 链式存储结构

线性表的链式存储结构是用一组 任意存储单元 来存放表中的数据元素,这组存储单元可以是连续的可以是不连续的。为了表示出每个元素与其后继元素之间的关系,除了存储本身的信息外,还需要存储一个指示其后继的存储位置信息

单向链表



顺序表是一种静态存储结构,而链表是一种动态存储结构。

若线性链表的每个节点只含有一个指针域, 则这样的链表称为单链表。

单链表的插入运算

/*
建立链表时,首先要建立表头节点,此时为空链表。然后将新的节点逐一插入到链表中
Step1: 申请存储单元 malloc (sizeof(Node))
Step2: 读入该节点的数据 新节点的指针域为空
Step3: 把新节点链接到链表上去(前插 后插)
*/

#include<malloc.h>
#include<stdio.h>
typedef struct node{
	int data;
	struct node * next;
}NODE;

NODE * create(){/*此函数采用后插入方式建立单链表,并返回一个指向链表表头的指针*/
	NODE *head,*q,*p;/*定义指针变量*/
	char ch;
	int a;
	head = (NODE *)malloc(sizeof(NODE));/*申请新的存储空间,建立表头节点*/
	q = head;
	ch = '*';
	printf("\n Input the list:");
	while(ch!='?'){
		scanf("%d",&a); /*输入新元素*/
		p = (NODE *)malloc(sizeof(NODE));

		p->data = a; /*将新节点数据赋值给p*/
		q->next =p;  /*将 指针p 赋值给他的前一个指针*/
		q = p;	/* 指针p赋值给指针q */

		ch = getchar();
	}
	q -> next =NULL;/*新节点的指针域为空*/
	return (head); /*返回表头指针head*/
}

main(){
	NODE *a;
	a= create();
	printf("out put the list");
	a = a->next;
	while(a!=NULL){
		printf("% d",a->data);
		a = a->next;
	}
}

单链表中的查找


locate()  按值查找 find 按序号查找

/*
建立链表时,首先要建立表头节点,此时为空链表。然后将新的节点逐一插入到链表中
Step1: 申请存储单元 malloc (sizeof(Node))
Step2: 读入该节点的数据 新节点的指针域为空
Step3: 把新节点链接到链表上去(前插 后插)
*/

#include<malloc.h>
#include<stdio.h>
typedef struct node{
	int data;
	struct node * next;
}NODE;

NODE * create(){/*此函数采用后插入方式建立单链表,并返回一个指向链表表头的指针*/
	NODE *head,*q,*p;/*定义指针变量*/
	char ch;
	int a;
	head = (NODE *)malloc(sizeof(NODE));/*申请新的存储空间,建立表头节点*/
	q = head;
	ch = '*';
	printf("\n Input the list:");
	while(ch!='?'){
		scanf("%d",&a); /*输入新元素*/
		p = (NODE *)malloc(sizeof(NODE));

		p->data = a; /*将新节点数据赋值给p*/
		q->next =p;  /*将 指针p 赋值给他的前一个指针*/
		q = p;	/* 指针p赋值给指针q */

		ch = getchar();
	}
	q -> next =NULL;/*新节点的指针域为空*/
	return (head); /*返回表头指针head*/
}

NODE * locate(NODE *head,int x){//在已知链表中查找给定的值x
	NODE *p;
	p = head->next;
	while((p!=NULL)&&(p->data!=x))//未到表尾且未找到数据
		p = p->next;
	return p;
}

NODE * find(NODE *head,int i){ /*在已知链表中查找序号为i的节点*/
	int j =1;
	NODE *p;
	p = head ->next;
	while((p!=NULL)&& (j<i)){
		p = p->next;
		j++;
	}
	return p;
}

main(){
	int y;
	NODE *a,*b;
	a = create();
	printf("Input x:");
	scanf("%d",&y);
	//b= locate(a,y);
	b = find(a,y);
	if(b!=NULL){
		printf("find:");
		printf("% 5d",b->data);
	}else
		printf("error");	
}


单链表的插入和删除

#include<malloc.h>
#include<stdio.h>
typedef struct node{
	int data;
	struct node *next;
}NODE;

NODE *create(){ /*此函数采用后插入的方式建立单链表,并返回一个指向链表表头的指针*/
	NODE * head,*q,*p;
	int a,n;
	head = (NODE *)malloc(sizeof(NODE)); /*申请新的存储空间,建立表头节点*/
	q = head;
	printf("\n Input number of the list");
	scanf("%d",&n);/*输入单向链表的个数*/

	if(n>0){
		printf("Input the list:");
		while(n>0){
			scanf("%d",&a);/*输入新元素*/
			p = (NODE*)malloc(sizeof(NODE));
			p ->data = a;
			q ->next = p;
			q= p;
			n--;
		}
	}
	q->next = NULL;
	return (head);
}
void insert(NODE *p,int x){/*在链表的p结点置后出插入给定的元素*/
	NODE *q;
	q = (NODE*)malloc(sizeof(NODE)); /*申请新的存储空间*/
	q ->data = x; /*将x赋值给开辟空间的q*/
	q ->next = p->next; /*p的下一个结点指针赋值给新增元素的下一个结点指针*/
	p->next = q;/*新增元素添加到链表中*/
}

void deleteList(NODE *head,int x){
	NODE *p,*q;
	q = head;
	p = q->next;
	while((p!=NULL)&&(p->data !=x)){ /*查找要删除的元素*/
		q = p;
		p = p->next;
	}
	if(p==NULL)
		printf("%d not found.\n",x);
	else{
		q->next = p->next;/*链接x直接后继结点*/
		free(p);/*删除x结点,释放x结点空间*/
	}
	

}
/* 
insertMain
main(){
	int x,position;
	int i=0,j = 0;
	NODE *c,*d;
	c = create();
	d = c->next;
	while(d!=NULL){
		d = d->next;
		j++;
	}
	d = c;
	do{
		printf("Input position (again:)");
		scanf("%d",&position); //position 可以为0表示表头结点
	}while(position>j || position<0);//position 值超过单向链表结点数,重新输入
	printf("Input x:");
	scanf("%d",&x);
	while(i!=position){//由postion确定其在单向链表中的位置d
		d = d->next;
		i++;
	}
	insert(d,x);
	printf("Output the list");
	while(c->next!=NULL){
		c = c->next;
		printf("%5d",c->data);
	}
	printf("ok");
}*/

main(){
	int x;
	NODE *a,*b;
	a = create();
	printf("Input x:");
	scanf("%5d",&x);
	deleteList(a,x);
	b = a ;
	b = b->next;
	printf("Output the list:");
	while(b!=NULL){
		printf("%5d",b->data); //输出删除x后的单向链表
		b = b->next;
	}
}

循环链表

单链表各结点之间由一个指针域链接,最后一个指针域的值用NULL表示,作为链表结束的标志。如果将单链表最后一个结点的指针指向头结点,使链表形成一个环形,此链表称为循环链表。



循环链表的插入和查找。

#include<malloc.h>
#include<stdio.h>
typedef struct node{//定义结点的存储结构
	int data;
	struct node *next;
}NODE;

NODE* create_circular(){//此函数采用后插入方式建立单向循环列表,并返回一个指向链表表头的指针
	NODE * head,*q,*p;
	int a,n;
	head = (NODE*)malloc(sizeof(NODE)); /*申请新的存储空间,建立表头节点*/
	q = head;
	printf("\n Input number of the list");
	scanf("%d",&n);/*输入单向链表的个数*/
	head ->data=n; //表头结点赋值n 即表中结点个数
	if(n>0){
		printf("Input the list:");
		while(n>0){
			scanf("%d",&a);/*输入新元素*/
			p = (NODE*)malloc(sizeof(NODE));
			p ->data = a;
			q ->next = p;
			q= p;
			n--;
		}
	}
	q->next = head; //将最后一个结点指向表头
	return (head);
}

NODE* prior(NODE*p){//返回结点p的直接前驱结点q,这里pq为结点指针
	NODE *q;
	q = p->next;
	while(q->next!=p)
		q = q->next;
	return (q);
}

main(){
	NODE* a,*c,*p;
	int i,j;
	a = create_circular();//建立单向循环链表
	printf("Input j:"); //给出单向循环链表中的结点序号,表头序号为0
	scanf("%d",&j);
	p = a;
	for(i=0;i<j;i++)
		p = p->next;//按序号确定一个p结点
	c = prior(p);
	printf("prior of %d is: %d",p->data,c->data);
}


双向链表

一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)


在单向循环链表中,虽然从任一已知结点都可以找到其前驱结点,但其时间复杂度为O(n),原因在于其每个结点只含有一个指向其后继结点的指针。若希望能快速找到一个结点的直接前驱,则可以在循环表中的结点中再增加一个指针域.这个指针直接指向该结点的直接前驱,这样 链表中的每一个结点就有了两个指针域.这称为双向链表。时间复杂度O(1)

双向链表的定义与操作运算

/* 线性表的双向链表存储结构 */
typedef struct DuLNode
{
  ElemType data;
  struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;

/*带头结点的双向循环链表的基本操作(14个) */
void InitList(DuLinkList *L)
{ /* 产生空的双向循环链表L */
  *L=(DuLinkList)malloc(sizeof(DuLNode));
  if(*L)
    (*L)->next=(*L)->prior=*L;
  else
    exit(OVERFLOW);
}

void DestroyList(DuLinkList *L)
{ /* 操作结果:销毁双向循环链表L */
  DuLinkList q,p=(*L)->next; /* p指向第一个结点 */
  while(p!=*L) /* p没到表头 */
  {
    q=p->next;
    free(p);
    p=q;
  }
  free(*L);
  *L=NULL;
}

void ClearList(DuLinkList L) /* 不改变L */
{ /* 初始条件:L已存在。操作结果:将L重置为空表 */
  DuLinkList q,p=L->next; /* p指向第一个结点 */
  while(p!=L) /* p没到表头 */
  {
    q=p->next;
    free(p);
    p=q;
  }
  L->next=L->prior=L; /* 头结点的两个指针域均指向自身 */
}

Status ListEmpty(DuLinkList L)
{ /* 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
  if(L->next==L&&L->prior==L)
    return TRUE;
  else
    return FALSE;
}

int ListLength(DuLinkList L)
{ /* 初始条件:L已存在。操作结果:返回L中数据元素个数 */
  int i=0;
  DuLinkList p=L->next; /* p指向第一个结点 */
  while(p!=L) /* p没到表头 */
  {
    i++;
    p=p->next;
  }
  return i;
}

Status GetElem(DuLinkList L,int i,ElemType *e)
{ /* 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR */
  int j=1; /* j为计数器 */
  DuLinkList p=L->next; /* p指向第一个结点 */
  while(p!=L&&jnext;
    j++;
  }
  if(p==L||j>i) /* 第i个元素不存在 */
    return ERROR;
  *e=p->data; /* 取第i个元素 */
  return OK;
}

int LocateElem(DuLinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ /* 初始条件:L已存在,compare()是数据元素判定函数 */
  /* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 */
  /*           若这样的数据元素不存在,则返回值为0 */
  int i=0;
  DuLinkList p=L->next; /* p指向第1个元素 */
  while(p!=L)
  {
    i++;
    if(compare(p->data,e)) /* 找到这样的数据元素 */
      return i;
    p=p->next;
  }
  return 0;
}

Status PriorElem(DuLinkList L,ElemType cur_e,ElemType *pre_e)
{ /* 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱, */
  /*           否则操作失败,pre_e无定义 */
  DuLinkList p=L->next->next; /* p指向第2个元素 */
  while(p!=L) /* p没到表头 */
  {
    if(p->data==cur_e)
    {
      *pre_e=p->prior->data;
      return TRUE;
    }
    p=p->next;
  }
  return FALSE;
}

Status NextElem(DuLinkList L,ElemType cur_e,ElemType *next_e)
{ /* 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继, */
  /*           否则操作失败,next_e无定义 */
  DuLinkList p=L->next->next; /* p指向第2个元素 */
  while(p!=L) /* p没到表头 */
  {
    if(p->prior->data==cur_e)
    {
      *next_e=p->data;
      return TRUE;
    }
    p=p->next;
  }
  return FALSE;
}

DuLinkList GetElemP(DuLinkList L,int i) /* 另加 */
{ /* 在双向链表L中返回第i个元素的地址。i为0,返回头结点的地址。若第i个元素不存在,*/
  /* 返回NULL */
  int j;
  DuLinkList p=L; /* p指向头结点 */
  if(i<0||i>ListLength(L)) /* i值不合法 */
    return NULL;
  for(j=1;j<=i;j++)
    p=p->next;
  return p;
}

Status ListInsert(DuLinkList L,int i,ElemType e)
{ /* 在带头结点的双链循环线性表L中第i个位置之前插入元素e,i的合法值为1≤i≤表长+1 */
  /* 改进算法2.18,否则无法在第表长+1个结点之前插入元素 */
  DuLinkList p,s;
  if(i<1||i>ListLength(L)+1) /* i值不合法 */
    return ERROR;
  p=GetElemP(L,i-1); /* 在L中确定第i个元素前驱的位置指针p */
  if(!p) /* p=NULL,即第i个元素的前驱不存在(设头结点为第1个元素的前驱) */
    return ERROR;
  s=(DuLinkList)malloc(sizeof(DuLNode));
  if(!s)
    return OVERFLOW;
  s->data=e;
  s->prior=p; /* 在第i-1个元素之后插入 */
  s->next=p->next;
  p->next->prior=s;
  p->next=s;
  return OK;
}

Status ListDelete(DuLinkList L,int i,ElemType *e)
{ /* 删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长 */
  DuLinkList p;
  if(i<1) /* i值不合法 */
    return ERROR;
  p=GetElemP(L,i);  /* 在L中确定第i个元素的位置指针p */
  if(!p) /* p=NULL,即第i个元素不存在 */
    return ERROR;
  *e=p->data;
  p->prior->next=p->next;//???没有考虑链表头?链表尾?
  p->next->prior=p->prior;
  free(p);
  return OK;
}

void ListTraverse(DuLinkList L,void(*visit)(ElemType))
{ /* 由双链循环线性表L的头结点出发,正序对每个数据元素调用函数visit() */
  DuLinkList p=L->next; /* p指向头结点 */
  while(p!=L)
  {
    visit(p->data);
    p=p->next;
  }
  printf("\n");
}

void ListTraverseBack(DuLinkList L,void(*visit)(ElemType))
{ /* 由双链循环线性表L的头结点出发,逆序对每个数据元素调用函数visit()。另加 */
  DuLinkList p=L->prior; /* p指向尾结点 */
  while(p!=L)
  {
    visit(p->data);
    p=p->prior;
  }
  printf("\n");
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值