2.1 线性表
线性表(List) 是由零个或多个数据元素的有限序列,允许在任意位置进行插入、删除和访问等操作。
线性表有两种物理结构——顺序存储结构和链式存储结构。
2.1.1 顺序存储结构
线性表的顺序存储结构是指,用一段地址连续的存储单元依次存储线性表的数据元素。
数组是一种使用顺序存储方式的数据结构。
首先是,线性表的顺序结构的结构代码:
#include<stdio.h>
#define MAX 66
//线性表所占存储空间的大小
typedef struct
{
int a[N];//此处int可换为其他数据类型,这主要看线性表元素的类型,数组a[N]用来存储线性表元素
int length;//线性表的当前长度
}List;
接着是,访问,访问有两种方式:
第一种是根据元素访问,返回元素的位置:(代码接上)
int getElem(List L,int x)//x为要查找的元素
{
int i=0;
while(i<=L.length&&L.a[i]!=x)//遍历线性表
i++;
if(i>L.length)//如果i比线性表的长度还大,则说明线性表没有此元素,返回-1
return -1;
else //找到返回它的位置
return i;
}
第二种是根据下标进行访问,返回元素本身:
int getElem(List L,int n)
{
int s = L.a[n];
return s;
}
然后是,增加:
bool Insert(List L,int s,int i)
{
if(L.length==MAX-1)
{
printf("表已满\n");
return false;
}
if(i<1||i>L.length+2)
{
printf("位序不合理\n");
return false;
}
for(int j=L.length;j>i;j--)
L.a[j]=L.a[j-1];
L.a[i]=s;
return true;
}
最后是,删除:
bool Delete(List L,int i)
{
if(i<1||i>=L.length)
{
printf("该数不存在!\n");
return false;
}
for(int j=i+1;j<L.length;j++)
L.a[j-1]=L.a[j];
return true;
}
2.1.2 链式存储结构
线性表的链式存储结构是指,用一组任意的存储单元存储线性表的数据元素,这组存储元素可以是连续的,也可以是不连续的。这就是说,这些元素可以存在着内存未被占用的任意位置。
链表也是使用链式存储方式实现的一种数据结构。
2.1.2.1 单向链表
首先定义单链表节点的结构体:
#include<stdio.h>
struct Node
{
int date;//数据元素
struct Node* next;//指向下一个节点的指针
};
创建节点:
struct Node* CreateList(int date)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));//给节点赋予空间
newNode->date = date; //数据域
newNode->next = NULL; //指针域
}
void BuildList(struct Node* head,int x)//x为元素
{
//创建新节点
struct Node* newNode = CreateList(x);
//找节点尾部
while(head->next!=NULL)
head = head->next;
//指向下一个节点
head->next = newNode;
}
接着是插入操作
void InsertNode(struct Node* head,int p,int x)//p为插入的位置,x为具体插入的值
{
struct Node* newNode = CreateNode(x);//新建一个节点
//last(链头的地址)---now(第一个数据地址)---...---NULL
struct Node* last = head;//last(上一个)地址为链头的地址
struct Node* now = head->next;//now(当前节点)地址为链头指向的地址
while(--p&&now!=NULL)//寻找要插入的位置(now)&&防止p大于链表的值
{
now = now->next;//迭代now节点
last = last->next;//迭代last节点
}
//插入操作
last->next = newNode;
newNode->next = now;
}
根据已知条件,删除方式分为两种:按位置删除和按元素删除
第一种是按位置删除:
void EraseNode(struct Node* head,int p)//p为要删除的位置
{
//与插入操作类似
struct Node* last = head;
struct Node* now = head->next;
while(--p&&now!=NULL)
{
now = now->next;
last = last->next;
}
//删除操作
last->next = now->next;
now->next = NULL;//不要忘记!!!
free(now);//释放空间
}
第二种是按元素删除:
void DeleteNode(struct Node* head,int x)
{
struct Node* last = head;
struct Node* now = head->next;
while(now!=NULL)
{
if(now->data==x)
{
last->next = now->next;
now->next=NULL;
free(now);
now = last->next;
}
else
{
now = now->next;
last = last->next;
}
}
}
接着是查找元素:
struct Node* FindNode(struct Node* head,int x)
{
struct Node* now = head->next;
while(now)
{
if(now->data==x)
{
return now;
}
now = now->next;
}
return NULL;
}
修改元素:
void ChangeNode(struct Node* head,int p,int x)
{
struct Node* now = head->next;
while(--p&&now!=NULL)
{
now = now->next;
}
if(!p)
now->data = x;
else
return;
}
2.1.3 单链表结构与顺序结构的优缺点
[1].单链表结构:
优点:
- 动态性,不需要预先知道存储空间的大小,可以动态的减少和增加,节省内存。
- 插入和删除操作便捷,仅仅只需要指针的指向即可,时间复杂度为O(1),效率高。
- 灵活性,可处理不同的数据类型。
缺点:
- 访问效率低,需要从头遍历进行查找。
- 内存消耗较多,结构上,与顺序存储结构相比,一个节点需要数据域和额外的指针域。
- 无法逆向遍历,指针方向是确定的。
[2].顺序结构:
优点:
- 访问效率高,通过下标就可以访问,时间复杂度为O(1),效率高。
- 存储空间利用率高,连续的存储空间,且没有额外的指针域,对于大型数据集合存储效率较高。
- 可逆向遍历。
缺点:
- 固定大小,需要预先确定数据大小。
- 插入和删除操作繁琐,插入和删除均要移动多个元素,效率低。
- 灵活性差,对于底层数据结构的操作有一定的限制,不太适合频繁的动态修改操作。
[3]. 有以上可知,当线性表需要频繁查找,插入和删除操作较少时,则使用顺序存储;当线性表的大小未知,或变化较大时,最好使用链表存储。
2.2 堆栈
堆栈的别称又叫栈,栈(Stack)是限定仅在栈顶(Top)进行插入和删除操作的线性表,也是线性表的特殊形式。因此它也有顺序和链式两种存储结构,它的主要操作有两种,入栈(Push)和出栈(Pop)。其主要特点是后进先出。
2.2.1 堆栈的顺序存储实现形式
首先是顺序存储结构的入栈(Push)与出栈(Pop)。
首先定义栈的结构
#include<stdio.h>
#define MAX 100
struct SqStack
{
int data[MAX];//data数组存放栈的数据
int top;//栈顶的位置
};
然后是入栈操作:
bool Push(SqStack S,int x)//x为新的栈顶元素
{
if(S.top==MAX-1)//判断是否栈满
return false;//若栈满则不能进栈
S.top++;
S.data[S.top-1] = x;
return true;
}
最后是出栈操作:
int Pop(SqStack S)
{
if(S.top==-1)//判断是否为空
return -1;
S.top--;
ShowSqStack(S);
return S.data[S.top]; //返回出栈元素
}
2.2.2 堆栈的链式存储实现形式(又作链栈)
接着是链式存储结构的出栈(Push)与入栈(Pop)。
同样的,首先需要定义一个链栈的结构
#include<stdio.h>
#include<stdlib.h>
struct StackNode
{
int data;//数据
struct StackNode* next;//下个节点的地址
};
然后创建一个空的链栈:
struct StackNode* CreateNode()
{
return NULL;
}
接着是入栈(Push)操作:
void Push(struct StackNode* Stack,int data)
{
struct StackNode* newNode = (struct StackNode*)malloc(sizeof(StackNode));//为每一个新节点分配空间
newNode->data = data;
newNode->next = Stack->next;//将新节点放在栈顶(新节点下一个地址指向Stack,说明新节点在Stack前面)
Stack->next = newNode;//迭代栈指针
}
由于出栈之前需要判断是否为空栈,所以先写一个函数判断:
bool isEmpty(struct StackNode* Stack)
{
return Stack->next==NULL;
}
接下来是出栈:
int Pop(struct StackNode* Stack)
{
if(isEmpty(Stack))
{
printf("栈空!不能做出栈操作\n");
return -1;
}
struct StackNode* top = Stack->next;
int popdata = top->data;
Stack->next = top->next;//不要忘记!!! 直接指向下一个节点
free(top);//释放空间
return popdata;
}
2.2.3 堆栈应用(表达式求值)
表达式一般由运算数和运算符号组成。
中缀表达式,即运算符号位于两个运算数之间的表达式。 这个就是我们平常写的算术表达式,例如:1+2*3.
后缀表达式,即运算符号位于两个运算数之后的表达式。这个表达式更方便计算,例如:123*+。(乘号“ * ”的优先级比加号“ + ”的优先级高,因此先算乘法)
后缀表达式的计算原则:碰到运算数就记住,遇到运算符号就与记住的最近的两个运算数做对应的计算。
中缀表达式要转后缀表达式,需要考虑运算符号的优先级。从左到右遍历中缀表达式的每个数字和符号,如果是数字就输出,如果是符号就判断它与栈顶符号的优先级,优先级高就输出。
(数字输出,运算符号就放进栈中)
2.3 队列
队列(Queue)是只允许在一端进行插入操作,另一端进行删除操作的线性表,也是线性表的特殊形式。主要是在列头(front)删除,在列尾(rear)增加。主要操作也就是入队和出队。
2.3.1 队列的顺序存储实现形式
#include<stdio.h>
#include<stdlib.h>
#define MAX 100
struct Queue
{
int data[MAX];//存数据
int front;//头指针
int rear;//尾指针
};
struct Queue* CreateQueue()//创建空列队
{
struct Queue* queue = (struct Queue*)malloc(sizeof(Queue));
queue->front = 0;
queue->rear = 0;
return queue;
}
bool isFull(struct Queue* q)//判断队满
{
return(q->rear==MAX-1);
}
void enQueue(struct Queue* q,int data)
{
if(isFull(q))
printf("队列满,不可再入队\n");
else
{
q->data[q->rear] = data;
q->rear++;
}
}
bool isEmpty(struct Queue* q)
{
return(q->rear==0);
}
int deQueue(struct Queue* q)
{
if(isEmpty(q))
{
printf("队列为空,不可删除\n");
return -1;
}
int dedata = q->data[q->front];
q->front++;
return dedata;
}
int main()
{
struct Queue* q = CreateQueue();
for(int i=0;i<5;i++)
{
q->data[i]=i;
}
q->rear = 5;
enQueue(q,6);
for(int i=0;i<q->rear;i++)
{
printf("列队元素为%d\n",q->data[i]);
}
printf("出队元素为%d\n",deQueue(q));
printf("头指针为%d,尾指针为%d\n",q->front,q->rear);
}
2.3.2 队列的链式存储实现形式
#include<stdio.h>
#include<stdlib.h>
//队列节点结构体
struct QNode
{
int data;
struct QNode* next;
};
//队列结构体
struct Queue
{
struct QNode* front;//头指针
struct QNode* rear;//尾指针
};
//创建空队列
struct Queue* CreateQueue()
{
//给队列和节点分配空间
struct Queue* queue = (struct Queue*)malloc(sizeof(struct Queue));
struct QNode* node = (struct QNode*)malloc(sizeof(struct QNode));
node->next = NULL;
queue->front = node;//头指针与尾指针都指向头节点
queue->rear = node;
return queue;
}
bool isEmpty(struct Queue* q)
{
return(q->front->next==NULL);//判断第一个节点是否为空
}
//出队操作,删除第一个节点并返回节点数据
int deQueue(struct Queue* q)
{
if(isEmpty(q))
{
printf("队列为空,不可删除\n");
return -1;
}
//注意!!!q->front->next才是第一个节点
struct QNode* frontCell = q->front->next;//保存第一个节点
int frontdata = frontCell->data;//保存第一个节点的数据
q->front = frontCell->next;//将头指针指向下一个节点
free(frontCell);//释放空间
return frontdata;
}
//入队操作,在队尾增加节点
void enQueue(struct Queue* q,int data)
{
//创建新节点
struct QNode* newNode = (struct QNode*)malloc(sizeof(QNode));//给节点分配空间
newNode->data = data;
newNode->next = NULL;
struct QNode* lastNode = q->rear;
lastNode->next = newNode;//将尾节点的指针指向新节点
q->rear = newNode;//将尾指针指向新节点
}
int main() {
struct Queue* q = CreateQueue();//创建一个空队列
//初始化队列
for (int i = 0; i < 5; i++) {
enQueue(q, i);
}
enQueue(q, 6);
//通过节点来遍历队列
struct QNode* n = q->front->next;//第一个节点
while (n != NULL) {
printf("队列元素为%d\n", n->data);
n = n->next;
}
printf("出队元素为%d\n",deQueue(q));//出队操作
printf("头指针为%d,尾指针为%d\n",q->front->data,q->rear->data);
return 0;
}
2.3.3 循环队列
队列一般的顺序存储结构会发生假溢出(尾指针已经到了最后,头指针并不是从0开始,数组data并没有满,但是已经无法增加元素的情况),为了解决这一问题,引出一个循环队列。循环队列是头尾相接的顺序存储结构。
循环队列在实现时,与一般队列的顺序存储结构不同的是,判断队满和队空的条件不同。
实现代码如下:
#include<stdio.h>
#include<stdlib.h>
#define MAX 100
struct Queue
{
int data[MAX];//存数据
int front;//头指针
int rear;//尾指针
};
struct Queue* CreateQueue()//创建空列队
{
struct Queue* queue = (struct Queue*)malloc(sizeof(Queue));
queue->front = 0;
queue->rear = 0;
return queue;
}
bool isFull(struct Queue* q)//判断队满
{
return((q->rear+1)%MAX==q->front);
}
void enQueue(struct Queue* q,int data)
{
if(isFull(q))
printf("队列满,不可再入队\n");
else
{
q->data[q->rear] = data;
q->rear = (q->rear+1)%MAX;
}
}
bool isEmpty(struct Queue* q)
{
return(q->front==q->rear);
}
int deQueue(struct Queue* q)
{
if(isEmpty(q))
{
printf("队列为空,不可删除\n");
return -1;
}
int dedata = q->data[q->front];
q->front = (q->front+1)%MAX;
return dedata;
}
int main()
{
struct Queue* q = CreateQueue();
for(int i=0;i<5;i++)
{
q->data[i]=i;
}
q->rear = 5;
enQueue(q,6);
for(int i=0;i<q->rear;i++)
{
printf("列队元素为%d\n",q->data[i]);
}
printf("出队元素为%d\n",deQueue(q));
printf("头指针为%d,尾指针为%d\n",q->front,q->rear);
}