线性表
数据结构中最常见的方法:数组存储/链表存储
线性表,由同类型数据元素构成的有序序列的线性结构
特点:
- 表中元素的个数—线性表的长度
- 表中没有元素时—空表
- 表起始的位置—表头
- 表结束的位置—表尾
数据对象集:
- 线性表时由n个元素组成的有序序列
主要操作:
初始化空表
查找位序
查找初次出现位置
在位序前插入新元素
删除指定位序的元素
返回表的长度
线性表顺序存储:利用数组的连续存储空间顺序存放线性表的各元素
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
typedef struct LNode* List;
//list 代表 Lnode*
typedef int ElementType;
const int MAXSIZE = 100;
struct LNode
{
ElementType Data[MAXSIZE];//定义数组
int last;//定义尾指针
};
struct LNode L;
List ptrl;//相当与LNode* ptrl
//访问下标为i的元素:L.Data[i]或Ptrl->Data[i]
//线性表的长度:L.Last+1或Ptrl->Last+1
//->时一个整体,用于指向结构体子数据的指针,用于读取子数据
//初始化(建立空的顺序表)
List MakeEmpty()
{
List ptrl;
ptrl = (List)malloc(sizeof(struct LNode));
//malloc:分配所需的内存空间,并返回一个指向它的指针
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;
else
return i;
}
//插入,插入第i个位置插入一个值,1<<i+1
//先移动再插入,每个i-1后的位置往后挪一位
//倒过来挪,从后面往后挪
void Insert(ElementType X,int i, List ptrl)
{
int j;
if (ptrl->last == MAXSIZE - 1)
{
cout << "表满" << endl;
return;
}
if (i<1 || i>ptrl->last + 2)
{
cout << "位置不合法" << endl;
return;
}
for (j = ptrl->last; i >= i - 1; j--)
{
ptrl->Data[j + 1] = ptrl->Data[j];
}//倒序移动,如果从前开始做会存在移动的元素自动覆盖当前值
ptrl->Data[i - 1] = X;
ptrl->last++;//last仍指向最后的位置
return;
}
//删除,删除表中第i个位置的元素
//i之后的元素全部往前挪,从前往后的顺序移动
void Destory(int i, List ptrl)
{
int j;
if (i<1 || i>ptrl->last + 1)
{
cout << "不存在第%d个元素"<< i << endl;
}
for (j = i; j <= ptrl->last; j++)
{
ptrl->Data[j-1] = ptrl->Data[j];
}
ptrl->last--;
return;
}
线性表的链式存储实现
不要求逻辑上相邻的两个元素物理上也相邻:通过“链"建立起数据元素之间的逻辑关系
- 利用数组存储的时候,两个元素不仅逻辑上相邻同时物理上也是相邻的
插入、删除操作不需要再移动数据元素,只需要修改”链“即可
每个节点都是一个结构,每个结构中包含两个分量,(当前节点的数据,下一节点的位置”指针“)
面临的挑战:
- 访问序号为i的元素
- 求线性表的长度
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
typedef struct LNode* List;
//list 代表 Lnode*
typedef int ElementType;
const int MAXSIZE = 100;
struct LNode {
ElementType Data;
List Next;
};
struct LNode L;
List ptrl;
//求表长
int Length(List ptrl)
{
List p = ptrl;//p指向表的第一个节点,临时的指针p指向链表的头
int j = 0;
while (p)//循环条件,p指针!=NULL
{
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)
return p;//找到第K个,返回指针
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个节点插入一个值为x的新节点
//先构建新节点
//找到链表的i-1节点
//修改指针,插入节点
//s->next=p->next
//p->next=s
//顺序不能交换,否者会出现值丢失的现象
//可使用ptrl=insert(...),通过调用insert,返回插入之后新链表的头指针
List Insert(ElementType X, int i, List ptrl)
{
List p, s;
if (i == 1)//新节点插入在表头
{
s = (List)malloc(sizeof(struct LNode));//申请,填装节点
s->Data = X;
s->Next = ptrl;
return s;//返回新表头指针
}
p = FindKth(i - 1, ptrl);//查找第i-1个节点
if (p == NULL)
{
cout << "参数错误" << endl;
return NULL;
}
else
{
s = (List)malloc(sizeof(struct LNode));
s->Data = X;
s->Next = p->Next;//p是查找到的i-1对应的节点
p->Next = s;
return ptrl;
}
}
//删除操作,删除链表中第i个位置上的节点
//先找到链表的第i-1节点,用p指向
//用s指针指向需要被删除的节点 s=p->next
//修改指针,删除s所指向的节点 p->next=s->next
//释放malloc申请的空间,释放s所指向节点的空间 ,free(s)
//顺序不可随意改变
List Delete(int i, List ptrl)
{
List p, s;
if (i == 1)
{
s = ptrl;
if (ptrl != NULL)
{
ptrl = ptrl->Next;
}
else
return NULL;//ptrl本身就是空的
free(s);//释放被删除的节点
return ptrl;
}
p = FindKth(i - 1, ptrl);
if (p == NULL)
{
cout << "第%d个节点不存在" << i - 1 << endl;
return NULL;
}
else if (p->Next == NULL)
{
cout<< "第%d个节点不存在" << i << endl;
return NULL;
}
else {
s = p->Next;
p->Next = s->Next;
free(s);
return ptrl;
}
}
广义表与多重链表
广义表是线性表的推广
线性表相当于一元多项式,广义表相当于二元及以上的多项式
对于广义表而言,n个元素都是基本的单元素
在广义表中,这些元素不仅可以是单元素也可以是另一个广义表
问题:
广义表构建中一个域有可能就是不能分解的单元,有可能是一个指针?
采用联合union,union可以把不同类型的数据组合在一起,把这一个空间理解成某种类型,也可理解成另外一种类型,为了区分不同类型,往往采用再弄个标记的方法进行处理
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
typedef struct GNode* GList;
typedef int ElementType;
const int MAXSIZE = 100;
struct GNode
{
int tag;//作为标记,tag=0表示节点为单元素,tag=1表示节点为广义表
union {//字表指针域sublist与单元数据域Data复用,即共用存储空间
ElementType Data;
GList sublist;
}URegion;
GList Next;//指向后继节点
};
多重链表
链表中的节点可能同时隶属于多个链,指针会有多个
一个结点包含了多个指针并不意味着是多重链表
-
多重链表中结点的指针域会有多个
-
包含两个指针域的链表并不一定是多重链表,例如,双向链表就不是多重链表
-
树,图等相对复杂的数据结构都可采用多重链表方式实现存储
例题:
矩阵可以用二维数组表示,但是
- 数组的大小需要事先确定
- 对于稀疏矩阵,会造成大量内存空间浪费
才用典型的多重链表——十字链表存储稀疏矩阵
-
只存储矩阵中非0元素项
-
结点的数据域:行指针ROW,列指针Col,数值Value
-
每个结点通过两个指针域,把同行、同列串起来
-
行指针Right
-
列指针Down
-
用一个标识符Tag区分头结点和非0元素结点
-
头结点的标识值为“head”,矩阵非0元素结点的标识符为“term”
堆栈
堆栈是一种线性结构,也是特殊的线性表
- 后缀表达式,运算符号位于两个运算数之后
- 中缀表达式,运算符号位于两个运算数之间
6 2/3-4 2*+ = 6/2-3+4x2
后缀表达式求值策略:从左向右“扫描”,逐个处理运算数和运算符号
- 碰到运算数的时候,将其记住
- 碰到运算符号的时候,将最近记住的两个数拿来做对应的运算
需要有种存储方法,能够顺序存储运算数,并在需要时“倒序”输出–>>先进后出,后进先出–>>堆栈
堆栈:具有一定操作约束的线性表,只能再一端(栈顶,Top)做插入、删除
- 插入数据:入栈(Push)
- 删除数据:出栈(Pop)
- 后入先出:Last In First Out (LIFO)
数据对象集:一个有0个或多个元素的有穷线性表
- 生成空堆栈
- 判断堆栈是否已满
- 将元素item压入堆栈
- 判断堆栈是否为空
- 删除并返回栈顶元素
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
const int MaxSize = 100;//存储数据元素的最大个数
typedef struct SNode* stack;
typedef int ElementType;
struct SNode {
ElementType Data[MaxSize];
int top;//指示栈顶在那个位置
};
//入栈
//Ptrs是stack类型的指针,也是结构指针
//top==-1代表堆栈空
//item需要放在top上面的一个位置
void Push(stack Ptrs, ElementType item)
{
if (Ptrs->top == MaxSize - 1)
{
cout << "堆栈满" << endl;
return;
}
else
{
Ptrs->Data[++(Ptrs->top)] == item;
//等价于(ptrs->top)++;
//ptrs->data[ptrs->top]=item;
return;
}
}
//出栈
ElementType Pop(stack Ptrs)
{
if (Ptrs->top == -1)
{
cout << "堆栈空" << endl;
return 0;
}
else
{
return(Ptrs->Data[(Ptrs->top)--]);
}
}
栈的顺序存储实现:
栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成
例题:
当一个数组用两个堆栈实现时,要求最大利用空间
- 让两个栈从数组的两头开始向中间生长
- 当两个栈的栈顶指针相遇,便代表两个栈都满了
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
const int MaxSize = 100;//存储数据元素的最大个数
typedef struct SNode* stack;
typedef int ElementType;
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)
{
if (Ptrs->top2 - Ptrs->top1 == 1)//判断两者是不是相邻即可,无需碰头
{
cout << "堆栈满" << endl;
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->top1 == -1)
{
cout << "堆栈空" << endl;
return NULL;
}
else
{
return Ptrs->data[(Ptrs->top1)--];
}
}
else
{
if (Ptrs->top2 ==MaxSize)
{
cout << "堆栈空" << endl;
return NULL;
}
else
{
return Ptrs->data[(Ptrs->top2)++];
}
}
}
堆栈的链式存储实现
栈的链式存储实际上就是一个单链表,或称链栈。插入和删除操作都只能在链栈的栈顶进行。
栈顶指针top应该用链表的那一头?应该在头结点位置,因为结点保存下一结点的指针
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
const int MaxSize = 100;//存储数据元素的最大个数
typedef struct SNode* stack;
typedef int ElementType;
struct SNode
{
ElementType Data;
struct SNode* Next;
};
//堆栈初始化(建立空栈)
//判断堆栈S是否为空
stack CreateStack()
{
//构建一个堆栈的头结点,返回指针
stack s;
s = (stack)malloc(sizeof(struct SNode));
s->Next = NULL;
return s;
}
int IsEmpty(stack s)
{
//判断堆栈s是否为空,若为空函数返回整数1,否则返回0
return(s->Next == NULL);
}
void Push(ElementType item, stack s)
{
//将元素item压入堆栈
struct SNode* Tmpcell;
Tmpcell = (stack)malloc(sizeof(struct SNode));
Tmpcell->Data = item;
Tmpcell->Next = s->Next;
s->Next = Tmpcell;
}
//数组中是含有具体的大小的,因此在使用的时候需要判别是否满了
//堆栈是不断申请内存空间,就无所谓是不是空的了
ElementType Pop(stack s)
{//删除并返回堆栈S的栈顶元素
struct SNode* Firstcell;
ElementType Topelem;
if (IsEmpty(s))
{
cout << "堆栈空" << endl;
return NULL;
}
else
{
Firstcell = s->Next;
s->Next = Firstcell->Next;
Topelem = Firstcell->Data;
free(Firstcell);
return Topelem;
}
}
堆栈应用:表达式求值
中缀表达式求值的应用
策略:将中缀表达式转换成后缀表达式进行求值
how?
-
运算数相对顺序不变
-
运算符号顺序发生改变
需要存储等待中的运算符号,要将当前运算符号与等待中的最后一个运算符号进行比较,堆栈解决运算符号的存储
- 碰到运算数就输出
- 碰到运算符号将其存起来,跟后头进行比较,再输出
ax(b+c)/d==adb+xd/
how to transfer 中缀表达式 to 后缀表达式?
从头到尾读取中缀表达式的每个对象
- 运算数,直接输出
- 左括号,压入栈顶
- 右括号,将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
- 运算符,如果优先级大于栈顶运算符时,把它压栈,如果优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出(计算),再比较i新的栈顶运算符,知道该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
- 若各对象处理完毕,则把堆栈中存留的运算符一并输出
堆栈的应用:
函数调用及递归实现l
深度优化搜索
回溯算法
当传递一个参数给函数的时候,这个参数会不会在函数内改变决定了使用什么参数类型
- 如果需要改变,则需要传递指向这个参数的指针
- 如果不用改变,可以直接传递这个参数
队列
队列与堆栈一样都是一种受限制的线性表
队列最主要的两个操作:
- 入队:加入队列中,从队尾开始,数据插入
- 出队:离开队列,从队头离开,数据删除
队列:具有一定操作约束的线性表
插入和删除操作:只能在一段插入,而在另一端删除
先进先出:FIFO
数据对象集:一个有0个或多个元素的有穷线性表
队列的顺序存储实现:
队列的顺序存储结构通常由一个一位数组和一个记录队列头位置的变量front以及一个记录队列尾元素位置的变量rear组成
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
const int MaxSize = 100;//存储数据元素的最大个数
typedef int ElementType;
struct QNode
{
ElementType data[MaxSize];
int rear;
int front;
};
//数组,队头,队尾
typedef struct QNode* Queue;
//front==rear 代表队列是空的
//由于队列即将满的时候会出现区分不明显的状态,为解决上述问题,可采用使用额外标记,size或者tag域
//或采用使用n-1个数组空间
//采用第二种方案
//入队
//实现循环队列的方法进行利用求余的特性
//%maxsize实现的便是循环队列的功能
void AddQ(Queue ptrq, ElementType item)
{
if ((ptrq->rear + 1) % MaxSize == ptrq->front)
{
cout << "队列满" << endl;
return;
}
ptrq->rear = (ptrq->rear + 1) % MaxSize;
ptrq->data[ptrq->rear] = item;
}
//出队
ElementType DeleteQ(Queue ptrq)
{
if (ptrq->front == ptrq->rear)
{
cout << "队列空" << endl;
return NULL;
}
else {
ptrq->front = (ptrq->front + 1) % MaxSize;
return ptrq->data[ptrq->front];
}
}
队列的链式存储实现
队列的链式存储结构也可以用一个单链表实现。插入和删除操作分别在链表的两头进行
front一般指向链表的头,rear一般指向链表的尾
#include<stdio.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
const int MaxSize = 100;//存储数据元素的最大个数
typedef int ElementType;
struct Node
{
ElementType Data;
struct Node* Next;
};
//每个结点所包含的信息
struct QNode//链表队结构
{
struct Node* rear;//指向队尾结点
struct Node* front;//指向队头结点
};
//代表队列
typedef struct QNode* queue;
queue ptrq;
//出队
ElementType DeleteQ(queue Ptrq)
{
struct Node* Frontcell;
ElementType frontelem;
if (Ptrq->front == NULL)
{
cout << "队列空" << endl;
return NULL;
}
Frontcell = Ptrq->front;
if (Ptrq->front == Ptrq->rear)//若队列只有一个元素,删除后队列置为空
Ptrq->front = Ptrq->rear = NULL;
else
Ptrq->front = Ptrq->front->Next;
frontelem = Frontcell->Data;
free(Frontcell);//释放被删除空间
return frontelem;
}
//入队
void InsertQ(queue Ptrq, ElementType item)
{
Node* q = (Node*)malloc(sizeof(Node));
q->Data = item;
q->Next = NULL;
if (Ptrq->rear == NULL)
{
Ptrq->rear = q;
Ptrq->front = q;
}
else {
Ptrq->rear->Next = q;
Ptrq->rear = q;
}
}
多项式相加
采用不带头结点的单向链表,按照指数递减的顺序排列各项
算法思路:
两个指针P1和P2分别指向这两个多项式的第一个结点,不断循环
- P1->expon==p2->expon 系数相加,p1和p2都分别指向下一项
- P1->expon>p2->expon ,将p1的结果保存,并让p1指向下一项
- p2亦然
当某一多项式处理完后,将剩下的对象是的所有节点依次复制到结果多项式中
小白专场——一元多项式的加法与乘法运算
- 多项式表示
- 仅表示非零项
- 数组–>编程简单、调试容易,需事先确定数组大小
- 链表–>动态性比较强,编程略为复杂、调试比较困难
- 本题已经告知多少项,可采用,动态数组的方法进行实现–结构数组
- 程序框架
- 读取多项式
- 加法实现
- 乘法实现
- 多项式输出