ch2 线性结构

73 阅读15分钟

线性表

定义

  • 表中元素个数称为线性表的长度
  • 线性表没有元素时,称为空表
  • 表起始位置称为表头,表结束位置称表尾
  • 用来存储由n个元素构成的有序序列

操作

  • List MakeEmpty(): 初始化一个空线性表 L
  • ElementType FindKth(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

线性表的实现

顺序存储

  • 利用数组连续储存空间顺序存放线性表的各元素,各元素的物理地址是连续的。
#define MAXSIZE 100//Data数组大小
typedef int ElementType;//元素类型 可定义为任意类型
typedef struct LNode* List;
struct LNode
{
    ElementType Data[MAXSIZE];
    int Last;//线性表的最后一个元素的下标
};

List L;
//访问下标为 i 的元素:L->Data[i]
//线性表的长度:L->Last+1
  • 初始化操作
List MakeEmpty() {
    List L;
    L = (List)malloc(sizeof(struct LNode));//为顺序表分配内存
    L->Last = -1;//顺序表是空的
    return L;
}
  • 按值查找操作
int Find(ElementType X, List L) {
    int i = 0;
    while (i <= L->Last && L->Data[i] != X) {//如果找到x就跳出循环 没找到x就遍历
        i++;
    }
    if (i > L->Last) {//没找到 返回-1
        return -1;
    }
    else return i;//找到 返回储存位置
}
  • 插入操作(在第i个位置插入新元素前 需将i以后的元素都后移一位 腾出位置)
void Insert(ElementType X, int i, List L) {
    if (L->Last == MAXSIZE - 1) {//表空间已满 无法插入
        printf("表满无法插入");
        return;
    }
    if (i < 0 || i > L->Last + 1) {//检查插入位置的合法性
        printf("位置不合法");
        return;
    }
    for (int j = L->Last; j >= i; j--) {//从后往前依次向后移一位 给新元素腾出位置
        L->Data[j + 1] = L->Data[j];
    }
    L->Data[i] = X;//插入新元素
    L->Last++;//Last仍指向最后元素
}
  • 删除操作(删除第i位的元素后 需要将i以后的元素向前移一位 补上空缺)
void Delete(int i, List L) {//删除
    if (i < 0 || i > L->Last) {//检查空表及删除位置的合法性
        printf("不存在第%d个元素", i);
        return;
    }
    for (int j = i; j <= L->Last; j++) {//将i位后的元素依次向前移一位 补上空缺
        L->Data[j] = L->Data[j + 1];
    }
    L->Last--;//Last指向最后元素
    return;
}
  • 按序查找操作
ElementType FindKth(int K, List L) {
    if (K < 0 || K > L->Last) {//位置越界
        printf("L->Data[%d]不存在元素", K);
        return;
    }
    return L->Data[K];
}
  • 返回表长操作
int Length(List L){ 
    return L->Last+1; 
}

链式存储

  • 不要求物理空间上相邻,只通过指针来建立逻辑的链条,因此删除插入等操作只需修改链而无需移动数据元素。
typedef int ElementType;//元素类型 可定义为任意类型
typedef struct LNode* List;
struct LNode
{
    ElementType Data;
    List Next;//下一个节点的指针
};

List L;
  • 初始化操作
List MakeEmpty() {
    List L = (List)malloc(sizeof(struct LNode));
    L = NULL;
    return L;
}
  • 按值查找操作
List Find(ElementType X, List L) {
    List p = L;
    while (p != NULL && p->Data != X) {
        p = p->Next;
    }
    return p;
    // 找到了返回p
    // 未找到返回NULL 此时p等于NULL 
}
  • 按序查找操作
List FindKth(int K, List L) {
    List p = L;
    int i = 1;
    while (p != NULL && i < K) {
        p = p->Next;
        i++;
    }
    if (i == K) return p;//找到了 返回p
    else return NULL;//没找到 返回空指针
}
  • 返回表长操作
int Length(List L) {
    List p = L;//p指向列表的第一个节点
    int j = 0;
    while (p) {
        p = p->Next;//当前p指向的是第j个节点
        j++;
    }
    return j;
}
  • 插入操作 在第i个节点的位置插入一个新节点 注意后两步顺序不能颠倒
    • 用s指向一个新节点
    • 用p指向第i-1个节点
    • 把第i个节点作为s的下一个节点
    • 把s作为p的下一个节点
List Insert(ElementType X, int i, List L) {
    List s, p;
    if (i == 1) {//在表头插入新节点
        s = (List)malloc(sizeof(struct LNode));
        s->Data = X;
        s->Next = L;
        return s;
    }
    p = FindKth(i - 1, L); //查找第i - 1个节点
    if (p == NULL) {//第i-1个节点不存在
        printf("参数i错误");
        return NULL;
    }
    else {
        s = (List)malloc(sizeof(struct LNode));//申请新节点
        s->Data = X;
        s->Next = p->Next;//新节点插入在第i-1个节点的后面
        p->Next = s;
        return L;
    }
}
  • 删除操作 删除第i个节点
    • p指向第i-1个节点
    • s指向第i个节点
    • 把s的下一个节点作为p的下一个节点
    • 释放s的内存空间
List Delete(int i, List L) {//删除第i个节点
    List p, s;
    if (i == 1) {//
        s = L;
        if (L->Next != NULL) L = L->Next;
        else return NULL;
    }
    p = FindKth(i - 1, L);//查找第i - 1个节点
    if (!p || !(p->Next)) {//第i个或第i-1个节点不存在
        printf("结点错误");
        return NULL;
    }
    else {
        s = p->Next;//s指向第i个节点
        p->Next = s->Next;//删除节点
        free(s);//释放删除节点的内存空间
        return L;
    }
}

广义表与多重链表

  • 广义表是线性表的推广,其元素不仅可以是单元素,也可以是另一个广义表
    image.png
  • 多重链表中的节点隶属于多个链,其节点有多个指针域(如图、树),但双向链表不是多重链表
    image.png

堆栈Stack

定义

  • 具有一定操作约束的有穷线性表,只在一端(栈顶 Top)作插入、删除,类似于摞盘子
  • 插入数据称为入栈Push,删除数据称为出栈Pop

操作

  • 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):删除并返回栈顶元素

image.png

堆栈的实现

顺序存储

  • 由一个一维数组和记录栈顶元素位置的变量组成
#define MaxSize 100//堆栈元素的最大个数
typedef int ElementType;//元素类型 可定义为任意类型
typedef struct SNode* Stack;
struct SNode {
	ElementType Data[MaxSize];
	int Top;//堆栈的栈顶指针
};
  • 初始化堆栈
Stack CreateStack() {
	S = (Stack)malloc(sizeof(struct SNode));
	S->Top = -1;//-1表示堆栈为空
	return S;
}
  • 是否已满
int IsFull(Stack S) {
	return (S->Top == MaxSize - 1);
}
  • 是否为空
int IsEmpty(Stack S) {
	return (S->Top == -1);
  • 入栈操作
void Push(Stack S, ElementType item) {
	if (IsFull(S)) {
		printf("堆栈已满");
		return;
	}
	else {
		S->Top++;//栈顶向上移一位
		S->Data[S->Top] = item;//放入栈顶
		return;
	}
}
  • 出栈操作
ElementType Pop(Stack S) {
	if (IsEmpty(S)) {
		printf("堆栈为空");
		return NULL;
	}
	else {
		ElementType val = S->Data[S->Top];//取出栈顶
		S->Top--;//栈顶向下移一位
		return val;
	}
}

实例:请用一个数组实现两个堆栈,要求最大限度利用数组空间,使数组只要有空间入栈就可以操作成功
思路:使这两个栈分别从数组两头向中间生长,当两个栈的顶端指针相遇时,表示这两个栈都满了

image.png image.png

链式存储

  • 用一个单链表(栈链)实现,插入和删除只在栈链的栈顶上进行。
  • 指针在栈顶,元素在栈顶后面。
  • 链表实现的堆栈是没有上限的,因此无需查看是否已满。
typedef int ElementType;//元素类型 可定义为任意类型
typedef struct SNode* Stack;
struct SNode {
	ElementType Data;//存放元素
	struct SNode* Next;//指向下一元素
};
Stack S;
  • 初始化堆栈
Stack CreateStack() {
	Stack S;
	S = (Stack)malloc(sizeof(struct SNode));
	S->Next = NULL;
	return S;
}
  • 是否为空
int IsEmpty(Stack S) {
	return (S->Next == NULL);
}
  • 入栈操作
void Push(Stack S, ElementType item) {//将元素item压入堆栈S
	struct SNode* TmpCell;//TmpCell是要压入的元素
	TmpCell = (Stack)malloc(sizeof(struct SNode));
	TmpCell->Data = item;
	TmpCell->Next = S->Next;//新入栈的元素在栈顶S后面
	S->Next = TmpCell;
}
  • 出栈操作
ElementType Pop(Stack S) {//删除并返回堆栈S的栈顶元素
	if (IsEmpty(S)) {
		printf("堆栈为空");
		return NULL;
	}
	struct SNode* Fiest;//First是栈顶元素
	ElementType val;
	Fiest = S->Next;//出栈的元素在栈顶S后面
	S->Next = Fiest->Next;//删除第一个元素
	val = Fiest->Data;//取出被删除元素的值
	free(Fiest);//释放空间
	return val;
}

堆栈的应用

  • 递归实现
  • 深度优先搜索
  • 回溯算法
  • 中缀表达式求值
  • ...

我们主要关注中缀表达式求值的问题
思路:将中缀表达式转换为后缀表达式,然后求值
image.png

image.png

队列Queue

定义

  • 具有一定操作约束的线性表,在一端插入,另一端删除,先进先出,类似于排队
  • 插入数据称为入队列,删除数据称为出队列

操作

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

实现

顺序存储

  • 由一个一维数组和两个变量组成,front指向队列第一个元素前面,rear指向队列最后一个元素,从rear入队,从front出队。由于无法区分队列是满还是空,因此仅使用n-1个数组空间。 image.png
#define MaxSize 100
typedef int ElemeentType;
typedef struct QNode* Queue;

struct QNode {//初始化
	ElemeentType Data[MaxSize];
	int rear;
	int front;
};
  • 初始化操作
Queue CreateQueue() {
	Queue Q;
	Q = (Queue)malloc(sizeof(struct QNode));
	Q->front = -1;
	Q->rear = -1;
	return Q;
}
  • 是否已满
int IsFull(Queue Q) {
	return ((Q->rear + 1) % MaxSize == Q->front);
}
  • 是否为空
int IsEmpty(Queue Q) {
	return (Q->front == Q->rear);
}
  • 新元素入队
void AddQ(Queue Q, ElemeentType item) {
	if (IsFull(Q)) {
		printf("队列满");
		return;
	}
	Q->rear = (Q->rear + 1) % MaxSize;
	Q->Data[Q->rear] = item;
}
  • 队首元素出队
ElemeentType DeleteQ(Queue Q) {//元素队首出队
	if (IsEmpty(Q)) {
		printf("队列空");
		return NULL;
	}
	Q->front = (Q->front + 1) % MaxSize;
	return Q->Data[Q->front];
}

链式存储

  • 用一个单链表实现队列,插入和删除操作分别在链表的两头进行,链表不会满因此无需判断队列是否已满
    image.png
typedef int ElementType;
typedef struct QNode* Queue;
struct Node {
	ElementType Data;
	struct Node* Next;
};
struct QNode {//链队列结构
	struct Node* rear;//指向队尾节点
	struct Node* front;//指向队首节点
};
Queue Q;
  • 初始化操作
Queue CreateQueue() {
	Queue Q;
	Q = (Queue)malloc(sizeof(struct QNode));
	Q->front = NULL;
	Q->rear = NULL;
	return Q;
}
  • 是否为空
int IsEmpty(Queue Q){ return (Q->front == NULL); }
  • 新元素从队尾入队
void AddQ(Queue Q, ElementType item) {//新元素从队尾入队
	struct Node* node;
	node = (struct Node*)malloc(sizeof(struct Node));
	node->Data = item;
	node->Next = NULL;
	if (Q->rear == NULL) { //此时队列空 
		Q->rear = node;
		Q->front = node;
	}
	else {//队列不为空 
		Q->rear->Next = node; //将结点入队 
		Q->rear = node;   // rear 仍指向最后 
	}
}
  • 元素从队首出队
ElementType DeleteQ(Queue Q) {
	struct Node* FrontCell;
	ElementType FrontElem;
	if (IsEmpty(Q)) {
		printf("队列空");
		return 0;
	}
	FrontCell = Q->front;
	if (Q->front == Q->rear) { // 队列中只有一个元素 
		Q->front = Q->rear = NULL;//删除后队列为空
	}
	else {
		Q->front = Q->front->Next;
	}
	FrontElem = FrontCell->Data;
	free(FrontCell);//释放被删除节点空间
	return FrontElem;
}

应用

多项式加法运算

  • 采用不带头节点的单项链表,按照指数递减的顺序排列各项
  • 思路:相同指数的项系数相加,其余部分进行拷贝
    image.png
#include<stdio.h>
#include<malloc.h>

struct PolyNode {
	int coef;//系数
	int expon;//指数
	struct PolyNode* link;//指向下一个结点的指针
};
typedef struct PolyNode* Polynomial;
Polynomial P1, P2;//待处理的两个多项式

int Compare(int e1, int e2) {//比较两个多项式中某一项的系数
	if (e1 > e2) return 1;
	else if (e1 = e2) return 0;
	else return -1;
}

void Attach(int c, int e, Polynomial* pRear) {//添加结果新节点
	Polynomial P;
	P = (Polynomial)malloc(sizeof(struct PolyNode));
	P->coef = c;//对新结点赋值
	P->expon = e;
	P->link = NULL;
	(*pRear)->link = P;//在原结果后拼接新节点
	*pRear = P;//尾指针指向新节点
}

Polynomial PolyAdd(Polynomial P1, Polynomial P2) {
	Polynomial front, rear, temp;
	int sum;
	rear = (Polynomial)malloc(sizeof(struct PolyNode));//为方便拼接处理 初始为rear申请一个空节点
	front = rear;//front指向初始空节点
	while (P1 && P2) {//当两个多项式都有非零项要处理时
		switch (Compare(P1->expon, P2->expon)) {
		case 1:
			Attach(P1->coef, P1->expon, &rear);
			P1 = P1->link;
			break;
		case -1:
			Attach(P2->coef, P2->expon, &rear);
			P2 = P2->link;
			break;
		case 0:
			sum = P1->coef + P2->coef;
			if (sum) Attach(sum, P1->expon, &rear);
			P1 = P1->link;
			P2 = P2->link;
			break;
		}
		//将未处理完的另一个多项式的所有节点依次复制到结果多项式中去
		for (; P1; P1 = P1->link) Attach(P1->coef, P1->expon, &rear);
		for (; P2; P2 = P2->link) Attach(P2->coef, P2->expon, &rear);
		rear->link = NULL;
		temp = front;
		front = front->link;//令front指向结果多项式的第一个非零项
		free(temp);//释放临时空表头节点
		return front;
	}
}

习题

两个有序链表序列的合并

L1L2是给定的带头结点的单链表,其结点存储的数据是递增有序的;函数Merge要将L1L2合并为一个非递减的整数序列。应直接使用原序列中的结点,返回归并后的带头结点的链表头指针。

输入样例
3
1 3 5
5
2 4 6 8 10
输出样例
1 2 3 4 5 6 8 10
NULL
NULL

#include <stdio.h>
#include <stdlib.h>

typedef int ElementType;
typedef struct Node *PtrToNode;
struct Node {
    ElementType Data;
    PtrToNode   Next;
};
typedef PtrToNode List;

List Read(); /* 细节在此不表 */
void Print( List L ); /* 细节在此不表;空链表将输出NULL */

List Merge( List L1, List L2 );

int main()
{
    List L1, L2, L;
    L1 = Read();
    L2 = Read();
    L = Merge(L1, L2);
    Print(L);
    Print(L1);
    Print(L2);
    return 0;
}

/* 你的代码将被嵌在这里 */
List Merge(List L1,List L2){
    List L = (List)malloc(sizeof(List));//申请新链表
    List head = L;//保存头节点
    List l1 = L1->Next;//头节点的下一个才开始储存数据
    List l2 = L2->Next;
    while(l1&&l2){//l1和l2不为空时循环比较,拼接到新链表后面
        if(l1->Data <= l2->Data){
            L->Next = l1;
            l1=l1->Next;
        }else{
            L->Next = l2;
            l2=l2->Next;
        }
        L = L->Next;
    }
    if(l1){//l1剩下的节点都拼到新链表后面
        L->Next = l1;
    }
    if(l2){//l2剩下的节点都拼到新链表后面
        L->Next=l2;
    }
    L1->Next = NULL;//拼接完成后原链表指向空
    L2->Next = NULL;
    return head;
}

一元多项式的乘法与加法运算

题目
输入两个多项式,分成两行
第一位数字是多项式的项数,后面是每一项的系数与指数,用空格分隔。
输出为两行,分别为两个多项式的系数与指数。

输入样例
4 3 4 -5 2 6 1 -2 0
3 5 20 -7 4 3 1
输出样例
15 24 -25 22 30 21 -10 20 -21 8 35 6 -33 5 14 4 -15 3 18 2 -6 1
5 20 -4 4 -5 2 9 1 -2 0

思路
程序的框架应是先读入两个多项式,再调用乘法运算并输出结果,调用加法运算并输出结果。
加法部分在上面有过讲解,不再赘述。
乘法部分可选择把乘法转换为加法:用一个多项式中的某一项乘以另一个多项式,再把结果相加
或者逐项插入:将P1当前项(c1,e1;)乘P2当前项(c2,e2;),并插入到结果多项式中,关键是要找到插入位置。初始结果多项式可由P1第一项乘P2获得

#include<stdio.h>
#include<malloc.h>
typedef struct Node *List;
struct Node{
    int coef;//系数
    int expon;//指数
    List next;
};
List t1,t2;

void Attach(int c,int e,List *l){
    List n = (List)malloc(sizeof(struct Node));
    n->coef = c;
    n->expon = e;
    n->next = NULL;
    (*l)->next = n;
    *l = n;
}

List Read(){
    List L,head;
    L = (List)malloc(sizeof(struct Node));
    L->next = NULL;
    head = L;
    int n;
    scanf("%d",&n);
    while(n--){
        int c,e;
        scanf("%d %d",&c,&e);
        Attach(c,e,&L);
    }
    head = head->next;
    return head;
}

List Add(List l1,List l2){
    List L,head;
    L = (List)malloc(sizeof(struct Node));
    L->next = NULL;
    head = L;
    int sum;
    t1=l1;
    t2=l2;
    while(t1&&t2){
        if(t1->expon == t2->expon){
            if(t1->coef+t2->coef !=0){
                sum = t1->coef+t2->coef;
                Attach(sum,t1->expon,&L);
            }
            t1 = t1->next;
            t2 = t2->next;
        }
        else if(t1->expon > t2->expon){
            Attach(t1->coef,t1->expon,&L);
            t1=t1->next;
        }
        else{
            Attach(t2->coef,t2->expon,&L);
            t2=t2->next;
        }
    }
    if(t1) L->next = t1;
    if(t2) L->next = t2;
    head = head->next;
    return head;
}

List Multiply(List P1,List P2){
    List P, Rear, t1, t2, t;
    int c, e;
    t1 = P1; t2 = P2;
    if (!P1 || !P2) return NULL;//如果有一个为NULL,就得返回了。因为没法做乘法。
    P = (List)malloc(sizeof(struct Node));
    P->next = NULL;
    Rear = P;//Rear是指向尾项的指针
    while (t2) { // 先用P1的第1项乘以P2,得到P 
        Attach(t1->coef*t2->coef, t1->expon + t2->expon, &Rear);
        t2 = t2->next;
    }
    t1 = t1->next;
    while (t1) {
        t2 = P2; 
        Rear = P;
        while (t2) {
            e = t1->expon + t2->expon;
            c = t1->coef * t2->coef;
            //先判断是不是指向NULL
            while (Rear->next && Rear->next->expon > e) //遍历链表直到<=当前项
                Rear = Rear->next;
            if (Rear->next && Rear->next->expon == e) { //链表中存在与当前项相同指数的项,两项合并
                if (Rear->next->coef + c)//系数不为零
                    Rear->next->coef += c;
                else {
                    t = Rear->next;
                    Rear->next = t->next;
                    free(t);
                }
            }
            else {//链表中的项比当前项系数小,把当前项插入链表中
                t = (List)malloc(sizeof(struct Node));
                t->coef = c; t->expon = e;
                t->next = Rear->next;
                Rear->next = t; Rear = Rear->next;
            }
            t2 = t2->next;
        }
        t1 = t1->next;
    }
    t2 = P; P = P->next; free(t2);
    return P;
}

void Print(List l){
    if(!l){
        printf("0 0");
        return;
    }
    while(l){
        printf("%d %d",l->coef,l->expon);
        if(l->next) printf(" ");
        l=l->next;
    }
}

int main(){
    List l1,l2,L1,L2;
    l1 = Read();
    l2 = Read();
    L1 = Multiply(l1,l2);
    Print(L1);
    printf("\n");
    L2 = Add(l1,l2);
    Print(L2);
    return 0;
}

逆转链表

弹出序列