数据结构(1)—— 线性表
写在前面
为了考研,需要复习数据结构。而对于数据结构这门学科来说,写代码是非常必要的。用代码把一些常见的数据结构与算法实现一遍,非常有利于对于数据结构的理解。
于是今天是第一章,即线性表的实现。主要参考是王道的数据结构复习指导
与黑皮的严蔚敏的教材
。虽然代码文件后缀都是cpp,但写法还是C的写法,主要借用了cpp里的&
与bool
来方便书写。
静态存储的顺序表实现
注意点
静态存储的顺序表,其实就是用数组来实现,主要注意点在于数组的下标和顺序表的位序并不是一一对应的关系,而是差1(顺序表的位序 = 数组的下标 + 1)。要注意这点。
代码
/*
* @Description: 静态存储的顺序表实现
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-05 21:39:06
*/
#include<bits/stdc++.h>
#define maxSize 10
// 定义静态存储的顺序表结构
typedef struct{
int data[maxSize];
int length;
}SqList;
// 初始化
void initList(SqList &L){
// 将所有初值设置为0
for(int i = 0;i < maxSize;i++){
L.data[i] = 0;
}
// 长度设置为0
L.length = 0;
}
// 插入
bool listInsert(SqList &L,int i,int e){
// 判断i的范围是否有效
if(i < 1 || i>L.length+1){
return false;
}
// 判断存储空间是否已经满
if(L.length >= maxSize){
return false;
}
// 将第i个元素之后的元素后移
for(int j=L.length;j >= i;j--){
L.data[j] = L.data[j-1];
}
// 在位置i处放入e
L.data[i-1] = e;
L.length++;
return true;
}
// 打印顺序表
void printList(SqList &L){
printf("顺序表的元素为:\n");
for(int i = 0;i < L.length; i++){
printf("%d ",L.data[i]);
}
printf("\n");
}
// 删除
bool listDelete(SqList &L,int i,int &e){
// 判断i的范围
if(i < 1 || i > L.length){
return false;
}
// 将被删除的元素赋值给e
e = L.data[i-1];
// 将第i个位置后的元素前移
for(int j=i;j<L.length;j++){
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}
// 按值查找
int locateElem(SqList L,int e){
int i;
for(i = 0;i < L.length;i++){
if(L.data[i] == e){
return i+1;
}
}
return 0;
}
// 按位序查找
int getElem(SqList L,int i){
return L.data[i-1];
}
// 主程序测试
int main(){
SqList L;
// 初始化
initList(L);
printList(L);
// 插入几个元素看看
listInsert(L,1,1);
listInsert(L,2,2);
listInsert(L,3,22);
printList(L);
// 删除一个元素
int deleteElem = 0;
listDelete(L,1,deleteElem);
printList(L);
// 查找元素22
printf("元素22的所在位置为 -> %d\n",locateElem(L,22));
// 根据位序查找
printf("位序为1的位置为 -> %d\n",getElem(L,1));
return 0;
}
动态存储的顺序表实现
注意点
动态存储的顺序表,相当于是在结构体中存储一个指针,当容量不够的时候再去动态申请一波新的空间,把旧的移到新区域就好。其他实现与静态存储的顺序表差别不大。
代码
/*
* @Description: 动态存储的顺序表实现
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-06 19:18:08
*/
#include<bits/stdc++.h>
#define initSize 3
// 定义动态存储的顺序表结构
typedef struct{
int *data;
int maxSize;
int length;
}SeqList;
// 初始化顺序表
void initList(SeqList &L){
// 用malloc函数申请一片连续的存储空间
L.data = (int *)malloc(initSize * sizeof(int));
// 长度设为0
L.length = 0;
// 最大大小设为初始大小
L.maxSize = initSize;
}
// 动态增加长度
void increaseSize(SeqList &L,int len){
int *p = L.data;
// C
// L.data = (int *) malloc((L.maxSize + len) * sizeof(int));
// C++
L.data = new int[L.maxSize + len];
// 把数据复制到新区域
for(int i = 0;i < L.length;i++){
L.data[i] = p[i];
}
// 增加长度
L.maxSize += len;
// 释放原有空间
// C
// free(p);
// C++
delete p;
}
// 插入
bool listInsert(SeqList &L,int i,int e){
// 判断i的范围是否有效
if(i < 1 || i>L.length+1){
return false;
}
// 判断存储空间是否已经满
if(L.length >= L.maxSize){
// 如果已满,申请更多空间
// 一次多申请几个
increaseSize(L,5);
}
// 将第i个元素之后的元素后移
for(int j=L.length;j >= i;j--){
L.data[j] = L.data[j-1];
}
// 在位置i处放入e
L.data[i-1] = e;
L.length++;
return true;
}
// 打印顺序表
void printList(SeqList &L){
printf("顺序表的元素为:\n");
for(int i = 0;i < L.length; i++){
printf("%d ",L.data[i]);
}
printf("\n");
}
// 删除
bool listDelete(SeqList &L,int i,int &e){
// 判断i的范围
if(i < 1 || i > L.length){
return false;
}
// 将被删除的元素赋值给e
e = L.data[i-1];
// 将第i个位置后的元素前移
for(int j=i;j<L.length;j++){
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}
// 按值查找
int locateElem(SeqList L,int e){
int i;
for(i = 0;i < L.length;i++){
if(L.data[i] == e){
return i+1;
}
}
return 0;
}
// 按位序查找
int getElem(SeqList L,int i){
return L.data[i-1];
}
// 主程序测试
int main(){
SeqList L;
// 初始化
initList(L);
printList(L);
// 插入几个元素看看
listInsert(L,1,1);
listInsert(L,2,2);
listInsert(L,3,22);
printList(L);
// 此时已满,测试能不能继续插入
listInsert(L,2,30);
printList(L);
// 删除一个元素
int deleteElem = 0;
listDelete(L,1,deleteElem);
printList(L);
// 查找元素30
printf("元素30的所在位置为 -> %d\n",locateElem(L,30));
// 根据位序查找
printf("位序为1的位置为 -> %d\n",getElem(L,1));
return 0;
}
带头结点的单链表实现
注意点
这里实现的是带头结点的单链表,不带头结点的会在各个函数上有着区别,要注意这点。对于有头结点的单链表,需要注意判空条件为L -> next == NULL
,即头指针指向NULL,否则都为非空。其他需要注意的便是头插法与尾插法的实现了,这块为难点和重点。
代码
/*
* @Description: 单链表的实现
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-06 20:57:47
*/
#include<bits/stdc++.h>
// 定义带头结点的单链表
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
// 初始化单链表
bool initList(LinkList &L){
// 分配一个头结点
L = (LNode *) malloc(sizeof(LNode));
// 内存已满
if (L == NULL){
return false;
}
// 把头结点指向空
L -> next = NULL;
return true;
}
// 判断单链表是否为空
bool empty(LinkList L){
if(L -> next == NULL){
return true;
}
return false;
}
// 链表输出
void printList(LinkList L){
LNode *p = L -> next;
printf("单链表内的元素为:\n");
while(!empty(p)){
printf("%d ",p->data);
p = p->next;
}
// 最后一个元素虽指向NULL但仍有值,继续打印
printf("%d ",p->data);
printf("\n");
}
// 按位查找,返回第i个元素
LNode * getElem(LinkList L,int i){
if(i < 0){
return NULL;
}
LNode *p;
int j = 0;
p = L;
while(p != NULL && j < i){
p = p -> next;
j++;
}
return p;
}
// 按值查找
LNode * locateElem(LinkList L,int e){
LNode *p = L -> next;
// 循环遍历链表
while(p != NULL && p->data !=e){
p = p->next;
}
return p;
}
// 后插,在p结点之后插入元素e
bool insertNextNode(LNode *p,int e){
// 判断是否超出长度
if(p == NULL){
return false;
}
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL){
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
// 前插,在p结点之前插入元素e,O(1)
bool insertPriorNode(LNode *p,int e){
if(p == NULL){
return false;
}
LNode *s = (LNode *) malloc(sizeof(LNode));
if(s == NULL){
return false;
}
// 相当于把p的值给了s,把新的值e给了p,然后连接起来
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return true;
}
// 插入
bool listInsert(LinkList &L,int i,int e){
if(i < 1){
return false;
}
// 找到第i-1个结点
LNode *p = getElem(L,i-1);
// p结点之后插入元素e
return insertNextNode(p,e);
}
// 删除 O(n)
bool listDelete1(LinkList &L,int i,int &e){
if(i < 1){
return false;
}
// 查找前驱结点
LNode *p = getElem(L,i-1);
// q指向被删除结点
LNode *q = p->next;
// 把q从链中断开
p ->next = q->next;
// 释放空间
free(q);
return true;
}
// 删除 O(1)
// !有坑,在删除最后一个结点时会报错
bool listDelete2(LNode *p){
if (p == NULL){
return false;
}
LNode *q = p->next;
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
// 求链表长
int getLength(LinkList L){
int len = 0;
LNode *p = L;
while(p -> next != NULL){
p = p->next;
len++;
}
return len;
}
// 尾插法建立
LinkList listTailInsert(LinkList &L){
int x;
// 初始化
initList(L);
// 定义头尾指针
LNode *s = L;
LNode *r = L;
scanf("%d",&x);
while(x != 9999){
// 在r结点之后插入x
s = (LNode *) malloc(sizeof(LNode));
s -> data = x;
r -> next = s;
// 让r指针指向新的尾
r = s;
scanf("%d",&x);
}
r -> next = NULL;
return L;
}
// 头插法建立
LinkList listHeadInsert(LinkList &L){
LNode *s;
int x;
// 初始化L
initList(L);
scanf("%d",&x);
while(x != 9999){
// 把新结点插入到头部
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d",&x);
}
return L;
}
// 主函数测试
int main(){
// 测试尾插法
LinkList L1,L2;
listTailInsert(L1);
printList(L1);
// 测试头插法
listHeadInsert(L2);
printList(L2);
// 测试插入一个新结点
listInsert(L1,1,100);
listInsert(L2,3,200);
printList(L1);
printList(L2);
// 测试删除一个结点
int deletedNode1;
listDelete1(L1,3,deletedNode1);
LNode *deletedNode2 = getElem(L2,3);
listDelete2(deletedNode2);
printList(L1);
printList(L2);
// 测试查找结点
printf("在L1中查找100,下一个位置的值为 -> %d",locateElem(L1,100)->next->data);
return 0;
}
双向链表的实现
注意点
对于双向链表,与单链表不同的是多了一个前向指针,可以实现向前遍历这样的操作了。需要注意的是双向链表的判空条件以及插入时的临界条件。
代码
/*
* @Description: 双向链表的实现
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-07 21:32:35
*/
#include <bits/stdc++.h>
// 定义双向链表结构
typedef struct DLinkNode
{
int data;
// 前向指针与后向指针
DLinkNode *pre;
DLinkNode *next;
} DLinkNode, *DLinkList;
// 初始化双链表
bool initList(DLinkList &DL)
{
// 分配一个头结点
DL = (DLinkNode *)malloc(sizeof(DLinkNode));
// 内存已满
if (DL == NULL)
{
return false;
}
// 把头结点指向空
DL->pre = NULL;
return true;
}
// 创建
DLinkList createDLinkList(DLinkList &DL)
{
int x;
initList(DL);
DLinkNode *p;
DLinkNode *s;
p = DL;
scanf("%d", &x);
while (x != 9999)
{
s = (DLinkNode *)malloc(sizeof(DLinkNode));
s->data = x;
p->next = s;
s->pre = p;
p = s;
scanf("%d", &x);
}
s->next = NULL;
return DL;
}
// 打印
void printList(DLinkList DL)
{
DLinkNode *p = DL->next;
printf("双链表内的元素为:\n");
while (p->next)
{
printf("%d ", p->data);
p = p->next;
}
// 最后一个元素虽指向NULL但仍有值,继续打印
printf("%d ", p->data);
printf("\n");
}
// 头部插入
void insertListHead(DLinkList &DL, int data)
{
DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
pNode->data = data;
pNode->next = DL->next;
pNode->pre = DL;
if (pNode->next != NULL)
{
pNode->next->pre = pNode;
}
DL->next = pNode;
}
// 尾部插入
void insertListTail(DLinkList &DL, int data)
{
DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
pNode->data = data;
DLinkNode *pCur = DL;
while (pCur->next != NULL)
{
pCur = pCur->next;
}
pCur->next = pNode;
pNode->pre = pCur;
pNode->next = NULL;
}
// 获取链表长度
int getListLength(DLinkList DL)
{
DLinkNode *p;
int len = 0;
p = DL;
for (; p->next != NULL; p = p->next)
{
len++;
}
return len;
}
// 指定位置插入节点,前插
bool insertList(DLinkList &DL, int data, int index)
{
DLinkNode *pCur = DL;
int len = getListLength(DL);
if (index > len)
{
return false;
}
else if (index == 0)
{
insertListHead(DL, data);
return true;
}
else if (index == len)
{
insertListTail(DL, data);
return true;
}
DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
pNode->data = data;
int i = 0;
while (index--){
pCur = pCur->next;
}
pNode->next = pCur->next;
pCur->next = pNode;
pNode->pre = pCur;
pNode->next->pre = pNode;
return true;
}
// 删除指定值
bool deleteListNode(DLinkList &DL,int key){
DLinkNode *pPre = DL;
DLinkNode *pCur = DL->next;
while(pCur != NULL){
if(pCur->data == key){
if(pCur->next != NULL){
pCur->next->pre = pCur->pre;
}
pCur->pre->next = pCur->next;
pPre = pCur;
pCur = pCur->next;
free(pPre);
return true;
}else{
pPre = pCur;
pCur = pCur->next;
}
}
return false;
}
// 主函数测试
int main(){
DLinkList DL;
// 创建
createDLinkList(DL);
printList(DL);
// 从头插入一个值
insertListHead(DL,5);
printList(DL);
// 从尾插入一个值
insertListTail(DL,100);
printList(DL);
// 任意位置插入
insertList(DL,1000,2);
printList(DL);
// 删除值为100的结点
deleteListNode(DL,100);
printList(DL);
}
带头结点的单循环链表的实现
注意点
这里实现的是带头结点的单循环链表,循环链表在理解了其本质后其实是很好写的。主要就是把最后一个元素的next
指针指向了第一个元素。关于实现,需要注意循环链表的判空条件,以及插入时的操作。
代码
/*
* @Description: 循环单链表的实现
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-08 19:38:04
*/
#include<bits/stdc++.h>
// 定义循环单链表结构体
typedef struct CLNode{
int data;
CLNode *next;
}CLNode,* CLinklist;
// 初始化
bool initList(CLinklist &CL){
CL = (CLNode *)malloc(sizeof(CLNode));
if(CL == NULL){
return false;
}
// 循环链表初始化头结点指向本身
CL->next=CL;
return true;
}
// 判空
bool isEmpty(CLinklist CL){
// 指向本身即为空
if(CL->next == CL){
return true;
}
return false;
}
// 长度
int getLength(CLinklist CL){
CLNode *p = CL->next->next;
int len = 0;
// p未到表头时
while(p != CL->next){
len++;
p = p->next;
}
return len;
}
// 取元素
int getElem(CLinklist CL,int i,int *e){
CLNode *p = CL->next;
int j = 0;
if(i < 1 || i > getLength(CL)){
// 不合法,返回-1
return -1;
}
while(j < i){
j++;
p = p->next;
}
*e = p->data;
// 返回0代表成功
return 0;
}
// 定位元素
int locateElem(CLinklist CL,int e){
CLNode *p = CL->next->next;
int j = 0;
while(p != CL->next){
j++;
if(p->data == e){
return j;
}
p = p->next;
}
return -1;
}
// 插入元素
bool listInsert(CLinklist &CL,int i,int e){
CLNode *p = CL->next;
CLNode *s;
int j = 0;
if(i < 1 || i > getLength(CL) + 1){
return false;
}
while(j < i -1){
j++;
p = p->next;
}
s = (CLNode *)malloc(sizeof(CLNode));
s->data = e;
s->next = p->next;
p->next = s;
// 如果在表尾插入,则把新插入的元素当做头结点(第一个元素)
if(p == CL){
CL = s;
}
return true;
}
// 删除元素
bool listDelete(CLinklist &CL,int i,int &e){
CLNode *p = CL->next;
CLNode *q;
int j = 0;
if(i < 1 || i > getLength(CL)){
return false;
}
while(j < i - 1){
j++;
p = p->next;
}
q = p->next;
e = q->data;
p->next = q->next;
// 删除表尾元素,指针发生变化
if(q == CL){
CL = p;
}
free(q);
return true;
}
// 打印
void printList(CLinklist CL)
{
CLNode *p = CL->next->next;
printf("循环链表内的元素为:\n");
while (p != CL->next)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 主函数测试
int main(){
CLinklist CL;
initList(CL);
printList(CL);
// 插入几个元素
listInsert(CL,1,100);
listInsert(CL,2,2);
listInsert(CL,3,3);
listInsert(CL,4,4);
listInsert(CL,5,5);
printList(CL);
// 插入到表尾试试
listInsert(CL,5,1000);
printList(CL);
// 删除一个元素
int deletedElem;
listDelete(CL,3,deletedElem);
printList(CL);
}
总结
这里还缺双向循环链表的实现,其实这种实现也并不是很难的方式,读者可以去自行实现。在线性表这一块大量使用了指针,一定要好好理解指针的概念才能更好地实现代码。