代码改变世界

数据结构第二讲:线性结构

2015-06-15 15:48  星星之火✨🔥  阅读(573)  评论(0编辑  收藏  举报

参考:浙大数据结构(陈越、何钦铭)课件

1、线性表及其实现

有一个很好的问题可以方便的说明引入链表的好处,那就是一元多项式:f(x) = a0 + a1x + an-1xn-1 + anxn 的表示及运算(两个多项式相加/相减/相乘等),显然我们可以利用数组来解决这个问题,两个多项式相加就是两个数组对应分量相加。但是这引入了一个很严重的问题,如何表示多项式x + 3x2000呢?开这么大的数组明显会造成空间浪费。

解决上面遗留的问题的一个方法是用结构数组按指数大小有序存储,每一个数组元素维护两个信息:多项式每一项的系数和指数。执行相加操作的时候只需要从头开始比较两个多项式当前对应项的指数即可。但是还是有问题,数组的物理空间是连续的,如果我们的数据超过了程序可分配的最大连续空间,那就杯具了,这很自然的引入了我们的链表:

链表的存储结构,即每个节点包含系数和指数两个数据域以及一个指针域:

typedef struct PolyNode * Polynomial;
typedef struct PolyNode{
    int coef;
    int expon;
    Polynomial link;
}

多项式表示问题给我们的启示就是:

  • 同一个问题可以有不同的表示(存储)方法
  • 有一类共性问题:有序线性序列的组织和管理

线性表(Linear List):由同类型数据元素构成有序序列的线性结构

  • 表中元素个数称为线性表的长度
  • 线性表没有元素时,称为空表
  • 表起始位置称为表头,结束位置称为表尾

线性表基本操作有:

  • List MakeEmpty():初始化一个空线性表
  • ElementType FindeKth(int K, List L):根据位序K,返回相应元素
  • int Find(ElementType X, List L):在线性表L中查找X的第一次出现位置
  • void Insert(ElementType X, int i, List L):在位序i 前插入一个新元素X
  • void Delete(int i, List L):删除指定位序i 的元素
  • int Length(List L): 返回线性表L 的长度n

线性表的顺序存储实现(利用数组的连续存储空间顺序存放线性表的各个元素):

存储结构:

typedef struct{
	ElementType Data[MAXSIZE];
	int Last;
}List;
List L, *PtrL;

访问下标为i 的元素:L.Data[i] 或PtrL->Data[i]

线性表的长度:L.Last+1 或PtrL->Last + 1(Last 表示末尾元素的下标位置)

主要操作实现:

  • 初始化(建立空表)
List * MakeEmpty()
{
	List *PtrL;
	PtrL = (List *)malloc(sizeof(List));
	PtrL->Last = -1;
	return PtrL;
}
  • 查找
int Find(ElementType X, List *PtrL)
{
	int i = 0;
	while(i <= PtrL->Last && PtrL->Data[i] != X)
		i++;
	if(i > PtrL->Last)
		return -1; // 如果没找到,返回-1
	else 
		return i; // 找到后返回的是存储位置
}
  • 插入(第i(1 ≤ i ≤ n+1)个位置上插入一个值为X的新元素) 
void Insert(ElementType X, int i, List *PtrL)
{
	int j;
	if(PtrL->Last == MAXSIZE-1) // 表空间已满,不能插入
	{
		printf("表满");
		return;
	}
	if(i < 1 || i > PtrL->Last+2)
	{
		printf("位置不合法");
		return;
	}
	for(j = PtrL->Last; j >= i-1; j--)
		PtrL->Data[j+1] = PtrL->Data[j]; // 将ai~an倒序向后移动
	PtrL->Data[i-1] = X; // 新元素插入
	PtrL->Last++; // Last仍指向最后元素
	return;
}
  • 删除(删除表的第i(1 ≤ i ≤ n) 个位置上的元素)
void Delete(int i, List *PtrL)
{
	int j;
	if(i < 1 || i > PtrL->Last+1)
	{
		printf("不存在第%d个元素", i);
		return;
	}
	for(j = i; j <= PtrL->Last; j++)
		PtrL->Data[j-1] = PtrL->Data[j]; // 将ai+1~an顺序向前移动
	PtrL->Last--; // Last仍指向最后元素
	return;
}

线性表的链式存储实现(不要求逻辑上相邻的两个元素物理上也相邻,通过"链"建立起数据元素之间的逻辑关系):

存储结构:

typedef struct Node{
	ElementType Data;
	struct Node *Next;
}List;
List L, *PtrL;

主要操作实现

  • 求表长
int Length(List *PtrL)
{
	List *p = PtrL; // p指向表的第一个结点
	int j = 0;
	while(p)
	{
		p = p->Next;
		j++; // 当前p指向的是第j个结点
	}
	return j;
}
  • 按序号查找
List *FindKth(int K, List *PtrL)
{
	List *p = PtrL;
	int i = 1;
	while(p != NULL && i < K)
	{
		p = p->Next;
		i++;
	}
	if(i == K) // 找到第K个,返回指针
		return p; 
	else
		return NULL; // 否则返回空指针
}
  • 按值查找
List *Find(ElementType X, List *PtrL)
{
	List *p = PtrL;
	while(p != NULL && P->data != X)
		p = p->Next;
	return p;
}
  • 插入(在第i-1(1 ≤ i ≤ n+1)个结点后插入一个值为X的新结点)  
List *Insert(ElementType X, int i, List *PtrL)
{
	List *p, *s;
	if(i == 1) // 新结点插入在表头
	{
		s = (List *)malloc(sizeof(List)); // 申请、填装结点
		s->Data = X;
		s->Next = PtrL;
		return s;
	}
	p = FindKth(i-1, PtrL); // 查找第i-1个节点
	if(p == NULL) // 第i-1个不存在,不能插入
	{
		printf("参数i错");
		return NULL;
	}
	else
	{
		s = (List *)malloc(sizeof(List)); // 申请、填装结点
		s->Data = X;
		s->Next = p->Next; // 新结点插入在第i-1个结点的后面
		p->Next = s;
		return PtrL;
	}
}
  • 删除(删除链表的第i(1 ≤ i ≤ n)个位置上的结点)  
List *Delete(int i, List *PtrL)
{
	List *p, *s;
	if(i == 1) // 若要删除的是表的第一个结点
	{
		s = PtrL; // s指向第1个结点
		if(PtrL != NULL) 
			PtrL = PtrL->Next; // 从链表中删除
		else
			return NULL;
		free(s);
		return PtrL;
	}
	p = FindKth(i-1, PtrL); // 查找第i-1个结点
	if(p == NULL)
	{
		printf("第%d个结点不存在", i-1);
		return NULL;
	}
	else if(p->Next == NULL)
	{
		printf("第%d个结点不存在", i);
		return NULL;
	}
	else
	{
		s = p->Next; // s指向第i个结点
		p->Next = s->Next; // 从链表中删除
		free(s); // 释放被删除结点
		return PtrL;
	}
}

2、堆栈

栈有很多酷炫的应用:表达式求值,括号匹配,函数调用与递归实现,深度优先搜索,回溯算法,不一而足。

堆栈的抽象数据类型描述

堆栈:具有一定操作约束的线性表,只在一端(栈顶,Top)做插入、删除

插入数据:入栈(Push);删除数据:出栈(Pop);后入先出:List In First Out(LIFO)

基本操作有:

  • Stack CreateStack(int MaxSize):生成空堆栈,其最大长度为MaxSize
  • int IsFull(Stack S, int MaxSize):判断堆栈S是否已满
  • void Push(Stack S, ElementType item):将元素item压入堆栈
  • int IsEmpty(Stack S):判断堆栈S是否为空
  • ElementType Pop(Stack S):删除并返回栈顶元素

 栈的顺序存储实现(通常由一个一维数组和一个记录栈顶元素位置的变量组成):

#define MaxSize <储存数据元素的最大个数>
typedef struct{
	ElementType Data[MaxSize];
	int Top;
}Stack;

// 入栈
void Push(Stack *PtrS, ElementType item)
{
	if(PtrS->Top == MaxSize-1)
	{
		printf("堆栈满");
		return;
	}
	else
	{
		PtrS->Data[++(PtrS->Top)] = item;
		return;
	}
}

// 出栈
ElementType Pop(Stack *PtrS)
{
	if(PtrS->Top == -1)
	{
		printf("堆栈空");
		return ERROR; // ERROR是ElementType的特殊值,标志错误
	}
	else
		return (PtrS->Data[(PtrS->Top)--])
}
		

[实例]请用一个数组实现两个堆栈,要求最大的利用数组空间,使数组只要有空间入栈操作就可以成功。

  分析:一种比较聪明的方法是使这两个栈分别从数组的两头开始向中间生长;当两个栈的栈顶指针相遇时,表示两个栈都满了。

#define MaxSize <存储数据元素的最大个数>
struct DStack{
	ElementType Data[MaxSize];
	int Top1; // 堆栈1的栈顶指针
	int Top2; // 堆栈2的栈顶指针
}S;

S.Top1 = -1;
S.Top2 = MaxSize;

// 入栈
void Push(struct DStack *PtrS, ElementType item, int Tag)
{
	// Tag作为区分两个堆栈的标志,取值为1和2
	if(PtrS->Top2 - PtrS->Top1 == 1) // 堆栈满
	{
		printf("堆栈满");
		return;
	}
	if(Tag == 1) // 对第一个堆栈操作
		PtrS->Data[++(PtrS->Top1)] = item;
	else // 对第二个堆栈操作
		PtrS->Data[--(PtrS->Top2)] = item;
}

// 出栈
ElementType Pop(struct DStack *PtrS, int Tag)
{
	if(Tag == 1)
	{
		if(PtrS->Top == -1) // 堆栈1空
		{
			printf("堆栈1空"):
			return NULL;
		}
		else
			return  PtrS->Data[(PtrS->Top1)--];
	}
	else
	{
		if(PtrS->Top2 == MaxSize) // 堆栈2空
		{
			printf("堆栈2空");
			return NULL;
		}
		else
			return PtrS->Data[(PtrS->Top2)++];
			
	}
}

堆栈的链式存储实现:

  栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。值得思考的一个问题是:栈顶指针应该在链表的哪一头?

typedef struct Node{
	ElementType Data;
	struct Node *Next;
}LinkStack;
LinkStack *Top;

LinkStack *CreateStack()
{
	// 构建一个堆栈的头节点,返回指针
	LinkStack *S;
	S = (LinkStack *)malloc(sizeof(struct Node));
	S->Next = NULL;
	return S;
}

int IsEmpty(LinkStack *S)
{
	// 判断堆栈S是否为空,若为空返回整数1,否则返回0
	return S->Next == NULL;
}

void Push(ELementType item, LinkStack *S)
{
	// 将元素item压入堆栈S
	struct Node *TmpCell;
	TmpCell = (LinkStack *)malloc(sizeof(struct Node));
	TmpCell->Element = item;
	TmpCell->Next = S->Next;
	S->Next = TmpCell;
}

ElementType Pop(LinkStack *S)
{
	// 删除并返回堆栈S的栈顶元素
	struct Node *FirstCell;
	ElementType TopElem;
	if(IsEmpty(S))
	{
		printf("堆栈空");
		return NULL;
	}
	else
	{
		FirstCell = S->Next;
		S->Next = FirstCell->Next;
		TopElem = FirstCell->Element;
		free(FirstCell);
		return TopElem;
	}
}

3、队列 

队列(Queue):具有一定操作约束的线性表

  插入和删除操作:只能在一端插入,而在另一端删除。

数据插入:入队列(AddQ);数据删除:出队列(DeleteQ);先来先服务;先进先出:FIFO

队列的主要操作:

  • Queue CreateQueue(int MaxSize):生成长度为MaxSize的空队列
  • int IsFullQ(Queue Q, int MaxSize):判断队列Q是否已满
  • void AddQ(Queue Q, ElementType item):将数据元素item插入队列Q中
  • int IsEmpty(Queue Q):判断队列Q是否为空
  • ElementType DeleteQ(Queue Q):将队头数据元素从队列中删除并返回

队列的顺序存储实现:

  队列的顺序存储结构通常由一个一维数组和一个记录队头元素位置的变量front和一个记录队尾元素位置的变量rear组成。

#define MaxSize <储存数据元素的最大个数>
typedef struct{
	ElementType Data[MaxSize];
	int rear;
	int front;
}Queue;

// 入队列
void AddQ(Queue *PtrQ, ElementType item)
{
	if((PtrQ->rear+1) % MaxSize == PtrQ->front)
	{
		printf("队列满");
		return;
	}
	PtrQ->rear = (PtrQ->rear+1) % MaxSize;
	PtrQ->Data[PtrQ->rear] = item;
}

// 入队列
ElementType DeleteQ(Queue *PtrQ)
{
	if(PtrQ->front == PtrQ->rear)
	{
		printf("队列空");
		return ERROR;
	}
	else
	{
		PtrQ->front = (PtrQ->front+1) % MaxSize;
		return PtrQ->Data[PtrQ->front];
	}
}

队列的链式存储实现:

  队列的链式存储结构也可以用一个单链表实现。插入和删除操作分别在链表的两头进行;值得思考的一个问题是:队列指针front和rear应该分别指向链表的哪一头?

typedef struct Node{
	ElementType Data;
	struct Node *Next;
}QNode;
typedef struct{ // 链队列结构
	QNode *rear; // 指向队尾结点
	QNode *front; // 指向队头结点
}LinkQueue;
LinkQueue *PtrQ;
 
// 不带头结点的链式队列出队操作的一个示例
ElementType DeleteQ(LinkQueue *PtrQ)
{
	QNode *FrontCell;
	ElementType FrontElem;
	
	if(PtrQ->front == NULL)
	{
		printf("队列空");
		return ERROR;
	}
	FrontCell = PtrQ->front;
	if(PtrQ->front == PtrQ->rear) // 若队列只有一个元素
		PtrQ->front = PtrQ->rear = NULL; // 删除后队列置空
	else 
		PtrQ->front = PtrQ->front->Next;
	FrontElem = FrontCell->Data;
	free(FrontElem); // 释放被删除结点空间
	return FrontElem;
}

 (本文完).