数据结构之线性表

线性表

线性表的定义和基本操作

1.线性表的定义

  线性表是具有相同特性的数据元素的一个有序序列。该序列中所含元素的个数叫做线性表的长度,用n(n>=0)表示。

2.线性表的存储结构

  1. 顺序表
       顺序表就是把线性表中的所有元素按照其逻辑顺序,依次存储到从指定的存储位置开始的一块连续的存储空间中。一样,线性表中第一个元素的存储位置就是指定的存储位置,第i+1个元素的存储位置紧接在第i个元素的存储位置的后面。
  2. 链表
       在链表的存储中,每个结点不仅包含所存元素的信息,还包含元素之间逻辑关系的信息,如单链表中前驱结点包含后继结点的地址信息,这样就可以通过前驱结点中的地址信息找到后继结点的位置。
  3. 两种存储结构的比较
    1.顺序表(随机访问特性、要求连续的存储空间<空间一旦分配好就不可改变>)
    2.链表(不支持随机访问、结点存储空间利用率较顺序表稍低、支持存储空间动态分配)
    3.链表有以下形式:
    1>单链表
    带头结点的单链表
       带头结点的单链表中的有一个节点不存储信息,只作标志。
       不带头结点的单链表中每个结点都存储信息。
       头指针head指针都指向链表中的第一个节点。
    2>双链表
    带头结点的双链表
    相较于单链表多了指向前驱结点的指针域。
    3>循环单链表
    带头结点的循环单链表
       即将单链表的最后一个指针域指向链表的第一个节点即可。
    4>循环双链表
    带头结点的循环双链表
      循环双链表的构造源自于双链表,类似于循环单链表基于单链表。

3.顺序表和链表的比较(408考研常考)

(1)基于空间的比较:

  1. 存储分配的方式(顺序表的存储空间是一次性分配的,链表的存储空间是多次分配的)
  2. 存储密度(顺序表的存储密度=1,链表的存储密度<1)

(2)基于时间的比较:

  1. 存取方式(顺序表可以随机存取,链表只能顺序存取)
  2. 插入/删除时移动元素的个数(顺序表需要近一半元素,链表则不需要移动元素只需要修改指针即可)

线性表的实现

线性表的结构体定义

1.顺序表的结构体定义
// define the Sequence List
#define   MaxSize    1024	
int SeqList[MaxSize];
int Index;

typedef struct
{
	int data[MaxSize];
	int length;
}Sqlist;

  平时我们所使用的数组即为顺序表。

2.单链表结点定义
// define the List Node Structure
typedef struct LNode
{
	int data;  // data store the node's data field (default type int)
	struct LNode *next;  // point to successor node
}LNode;
3.双链表结点定义
// define the Double List Node Structure
typedef struct DLNode
{
	int data;  // data store the node's data field (default type int)
	struct DLNode *prior; // point to precursor node
	struct DLNode *next;  // point to successor node
}DLNode;

顺序表的操作

  顺序表即平时常用的数组,常用操作无非是插入删除查询等。
  这里列出了初始化、查询、插入、删除。(实现方法可有多种)

/*
 *  the operator of the Sequence List
 */
 
/*
 *	initList operator
 *  initializate the table
 */ 
void initList(Sqlist &L)
{
	L.length = 0;
}
/*
 *	findElem operator
 *  return the index of the first element equal to value
 */ 
int findElem(Sqlist L,int value)
{
	for(int i = 0; i < L.length; ++i)
	{
		if(value == L.data[i])
		{
			return i;
		}
	}
	return -1; // if the element is non-existent , then return -1 as the failure flag
}
/*
 *	insertElem operator
 *  insert the element to the table, input index and value
 */ 
int insertElem(Sqlist &L,int index,int value)
{
	// errer index or the list size is reach the maxsize 
	if(index < 0 || p > L.length || L.length == MaxSize)  
	{
		return 0;
	}
	for(int i = L.length-1, i >= index; --i)
	{
		L.data[i+1] = L.data[i]; // Move the elements in the table back one position
	}
	L.data[index] = value;
	++(L.length);
	return 1; // insert sucessfully
}
/*
 *	deleteElem operator
 *  delete the element in the table, input index ,return the deleted element
 */ 
int deletElem(Sqlist &L,int index,int &e)
{
	if(indx < 0 || index > L.length-1) // return 0 as failure flag if the position is error 
	{
		return 0;
	}
	e = L.data[index];
	for(int i = 0; i < L.length-1; ++i)
	{
		L.data[i] = L.data[i+1]; // Move the elements in the table forward one position
	}
	--(L.length);
	return 0; // delete sucessfully
}
/*
 *	getElem operator
 *  return the value of the input index element
 */ 
int getElem(Sqlist L,int index,int &e)
{
	if(indx < 0 || index > L.length-1) // return 0 as failure flag if the position is error 
	{
		return 0;
	}
	e = data[index];
	return 1; // get sucessfully
}

单链表操作

  将两个递增链表归并成递增/递减新链表,头插尾插操作以及查找删除结点

/*
 *  the operator of the Link List
 */
 
/*
 *  mergeIncreaseList operator
 *	merge A and B as C (use the insert of rear to create C) (C is increasing)
 *  (both A and B are increasing)
 */
void mergeIncreaseList(LNode *A,LNode *B,LNode *&C)
{
	LNode *p = A->next;
	LNode *q = B->next;
	LNode *r; // to point the rear of C
	C = A;    // use A's head node as C's
	C->next = NULL;
	free(B);  // release the Node B
	r = C;    // r point to C 
	while(p!=NULL && q!=NULL)  // while A or B still have elements
	{
		if(p->data <= q->data)
		{
			r->next = p; // chain p to the rear of C 
			p = p->next; // p move to next
			r = r->next; // r move to next
		}
		else
		{
			r->next = q; // chain q to the rear of C
			q = q->next; // q move to next
			r = r->next; // r move to next
		}
	}
	// the left of A or B , chain to the rear of C directly
	if(p != NULL) r->next = p;
	if(q != NULL) r->next = q;
}
 
/*
 *  mergeDecreaseList operator
 *	merge A and B as C (use the insert of head to create C) (C is decreasing)
 *  (both A and B are decreasing)
 */
void mergeDecreaseList(LNode *A,LNode *B,LNode *&C)
{
	LNode *p = A->next;
	LNode *q = B->next;
	LNode *s; // to point the rear of C
	C = A;    // use A's head node as C's
	C->next = NULL;
	free(B);  // release the Node B
	while(p!=NULL && q!=NULL)  // while A or B still have elements
	{
		if(p->data <= q->data)
		{
			s = p; 
			p = p->next; 
			s->next = C->next;
			C->next = s;
		}
		else
		{
			s = q;
			q = q->next;
			s->next = C->next;
			C->next = s;
		}
	}
	// the left of A or B , chain to the rear of C directly
	if(p != NULL) r->next = p;
	if(q != NULL) r->next = q;
}
/*
 *  createListByRear operator
 *	use the insert of rear to create
 */
void createListByRear(LNode *&C,int Arr[],int n)
{
	LNode *s,*r; // s as the new node, r point to the rear node
	C = (LNode *)malloc(sizeof(LNode));
	C->next = NULL;
	r = C;
	for(int i = 0; i < n; ++i)
	{
		s = (LNode *)malloc(sizeof(LNode));  // apply new node 
		s->data = Arr[i];
		r->next = s;
		r = r->next;
	}
	r->next = NULL;  // the r point to the last node  
}
/*
 *  createListByHead operator
 *	use the insert of head to create
 */
void createListByHead(LNode *&C,int Arr[],int n)
{
	LNode *s;
	C = (LNode *)malloc(sizeof(LNode));
	C->next = NULL;
	for(int i = 0; i < n; ++i)
	{
		s = (LNode *)malloc(sizeof(LNode));
		s->data = Arr[i];
		s->next = C->next; // s-> point to the start node of C
		C->next = s; // the head node point to s
	}
}
/*
 *  findAndDelete operator
 *	find the data equal to value's node then delete it
 */
int findAndDelete(LNode *C,int value)
{
	LNode *p,*q;
	p = C;
	while(p->next != NULL) // find the node->data equal to the value
	{
		if(p->next->data == value)
		{
			break;
		}
		p = p->next;
	}  
	if(p->next == NULL)
	{
		return 0; // the value is non-existence
	}
	else
	{
		q = p->next;
		p->next = p->next->next;
		free(q);
		return 1;  // delete sucessfully
	}
}

双链表操作

  头插尾插创建双链表/查询节点。

/*
 *  the operator of the Double Link List
 */
 
/*
 *  createDListByRear operator
 *	use the insert of rear to create
 */
void createDListByRear(DLNode *&L,int Arr[],int n)
{
	DLNode *s,*r;  // create new node s and rear pointer
	L = (DLNode *)malloc(sizeof(DLNode));
	L->prior = NULL;
	L->next = NULL;
	r = L;
	for(int i = 0; i < n; ++i)
	{
		s = (DLNode *) malloc(sizeof(DLNode));
		s->data = Arr[i];
		r->next = s;
		s->prior = r;
		r = s;
	}
	r->next = NULL;
}
/*
 *  createDListByHead operator
 *	use the insert of head to create
 */
void createDListByHead(DLNode *&L,int Arr[],int n)
{
	DLNode *s;
	L = (DLNode *)malloc(sizeof(DLNode));
	L->next = NULL;
	L->prior = NULL;
	for(int i = 0; i < n; ++i)
	{
		s = (DLNode *)malloc(sizeof(DLNode));
		s->data = Arr[i];
		L->next->prior = s;
		s->next = L->next;
		s->prior = L;
		L->next = s;
	}
}
/*
 *  findNode operator
 *	find node equal to value
 */
void findNode(DLNode *L,int value)
{
	DLNode *p = L->next;
	while(p != NULL)
	{
		if(p->data == value)
		{
			break;
		}
		return p;
	}
}

循环链表操作

  循环链表不论是循环单链表还是循环双链表都是可以由单链表和双链表微改而来,只需要在终端节点和头节点间建立联系即可。
  循环单链表呢,就是终端节点的next结点指针指向表头结点即可;
  循环双链表呢,就是终端节点的next结点指针指向表头,表头的prior指针指向表尾结点。
  看到这里已经提到了所有常用的链表啦,可以自己做一个简单的回顾,单链表双链表的一些基本操作呀,具体的实现步骤在脑海里面过一下!接下来可以跟着后面设下的问题来一起回顾下呢!

常见问题(不难,需要掌握)

提问:假设在p所指向的结点之后插入一个结点s,该如何操作呢?抑或是在p所指向的结点之前插入一个结点s,又该如何操作呢?
【分析】:
  很显然,只要稍微理解一下头插法跟尾插法就可以将此问题看作是两种插入方法的延伸,头插尾插即p指向的是头节点,特别需要注意的就是指针在连接的两个结点时,在操作过程中不能够使之产生缺失。(最重要就是不能让他们之间的连接断掉,只要节点之间能够互相找得到那么就没什么问题!)

下面具体可以看图示了解完整过程:
双链表在p结点之后插入结点s
这里只列出来了双链表中的在p结点之后插入s结点,同理也可以写出单链表的相应操作,动动手呀!

提问:若需要删除双链表中p结点的后继结点,该如何操作呢?
【分析】:
  还是那句话,只要涉及到操作结点指针,一定不能让节点之间的联系断开!
下面具体可以看图示了解完整过程:
删除双链表的后继结点
  单链表,循环单链表,循环双链表的删除节点操作,都可以自行试试呀!
提问:定义一个线性表,如将其中的元素逆置?
【分析】:
  可设置两个变量i、j,i指向第一个元素,j只想最后一个元素,边交换i和j所指向的元素,边让i、j相向而行,直至相遇。
代码比较容易理解:

for(int i = left,j = right;i < j; ++i,--j)
{
	swap(a[i]),a[j];
}

可以思考下列问题来巩固下元素逆置问题!

  • 将一个长度为n的数组前端k(k<n)个元素逆序后移动到数组后端,要求原数组中的数据不丢失,其余元素的位置无关紧要;
void reverse(int a[],int left,int right,int k)
{
	for(int i = left,j = right;i < left+k && i < j; ++i,--j)
	{
		swap(a[i]),a[j];
	}
}
  • 将一个长度为n的数组前端k(k<n)个元素保持原序后移动到数组后端,要求原数组中的数据不丢失,其余元素的位置无关紧要;
    逆置过程
void MoveToEnd(int a[],int n,int k)
{
	reverse(a,0,k-1,k);
	reverse(a,0,n-1,k);
}
  • 将数组中的元素(X0,X1,…,Xn-1),经过移动后变成(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1),即循环左移p(0<p<n)个位置。
void MoveP(int a[],int n,int p)
{
	reverse(a,0,p-1,p);
	reverse(a,p,n-1,n-p);
	reverse(a,0,n-1,n);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我蘑菇先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值