2)数据结构之线性表
Posted 流浪孤儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2)数据结构之线性表相关的知识,希望对你有一定的参考价值。
目录
线性表(linear list)
是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
顺序表属于线性表,那么在逻辑上必定是线性结构的,顺序表在物理结构上也是连续的
顺序表一般使用数组来实现,在数组上完成数据的增删查改。
顺序表又可以分为
静态顺序表:使用定长数组存储
动态顺序表:使用动态开辟的数组存储
静态顺序表用的少,一般我们所说的顺序表就是指动态顺序表
顺序表优点:支持随机存储
顺序表缺点:1、动态增容有性能消耗并且有一定程度的空间浪费(用realloc进行扩容可能会开辟新的空间再把原数据拷贝过去,增容后的空间一般是原空间的1.5倍或者2倍,有时候不需要那么多空间)
2、需要往头部插入数据,因此需要挪动数据,这一过程时间复杂度为0(N)
静态顺序表:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N];//定长数组
size_t size;//用来记录当前数组存储的数据数量
}SeqList;
int main()
{
SeqList sl;
sl.size = 0;
for (int i = 0; i < N; i++)
{
sl.array[i] = i;
sl.size++;
}
return 0;
}
动态顺序表
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表
首先先说明一下实现的大致思路:
第一步:创建动态顺序表
第二步:动态顺序表的初始化
第三步:动态顺序表的接口函数:增、删、查、改
第四步:动态顺序表的销毁
建立一个工程,并创建下面三个文件,其中
SeqList.h里包含了所需要的头文件,各种函数的声明,以及动态顺序表的创建 ,
SeqList.c包含了动态顺序表的接口函数的定义,
Test..c则是对各个函数的功能检测
SeqList.h文件中
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#define N 2//第一次开辟的内存空间大小
typedef int SLDataType;//SLDataType用来定义动态顺序表所存储的数据的类型
typedef struct SeqList//动态顺序表的创建(未初始化)
{
SLDataType *pList;//指向动态开辟的空间
size_t size;//记录当前顺序表存储的数据数量
size_t capacity;//记录当前顺序表的空间大小
}SeqList;
bool CheckEmptySeqList(SeqList *SList);//检查动态顺序表是否一个数据都没存
static void SeqListAppend(SeqList *SList, SLDataType data);//增容函数
void InitSeqList(SeqList *SList);//初始化动态顺序表
void PrintSeqList(SeqList *SList);//打印动态顺序表
void PushBackSeqList(SeqList *SList, SLDataType data);//尾插增加元素
void PushHeadSeqList(SeqList *SList, SLDataType data);//头插
void InsertSeqList(SeqList *SList, SLDataType data, int pos);//在pos坐标处插入元素
void DeleteBackSeqList(SeqList *SList);//尾删
void DeleteHeadSeqList(SeqList *SList);//头删
void DeleteMiddleSeqList(SeqList *SList, int pos);//删除pos坐标所在的元素
static int findpos(SeqList *SList, SLDataType data);//查找任意位置的元素的坐标,内部函数
void FindSeqList(SeqList *SList, SLDataType data);//将查找任意位置的元素的坐标的信息输出
void ModifySeqList(SeqList *SList, SLDataType data, int pos);//修改动态顺序表中的某一个元素
void DestroySeqList(SeqList *SList);//销毁动态顺序表
//#include<stdio.h>:包含了printf()函数
//#include<stdlib.h>:包含了malloc(),realloc()
//#include<assert.h>:包含了assert()函数
//#include<stdbool.h>:包含了bool类型,true与false就可以使用了
SeqList.c文件中
#include"SeqList.h"
void InitSeqList(SeqList *SList)
{
assert(SList);
SList->pList = (SLDataType*)malloc(sizeof(SLDataType)*N);
if (SList->pList == NULL)
{
perror("InitSeqList");
return;
}
SList->size = 0;
SList->capacity = N;
}
void PrintSeqList(SeqList *SList)
{
assert(SList);
assert(SList->pList);
for (int i = 0; i < SList->size; i++)
{
printf("%d ", SList->pList[i]);
}
printf("\\n");
}
static void SeqListAppend(SeqList *SList, SLDataType data)//增容函数
{
assert(SList);
assert(SList->pList);
if (SList->size == SList->capacity)
{
SLDataType *tmp = (SLDataType*)realloc(SList->pList, sizeof(SLDataType)*(SList->capacity * 2));
if (tmp == NULL)
{
perror("PushBackSeqList");
return;
}
SList->capacity *= 2;
SList->pList = tmp;
}
}
//以下注释的代码是因为可以复用其它函数进行优化使程序高内聚
//void PushBackSeqList(SeqList *SList, SLDataType data)
//{
// assert(SList);
// SeqListAppend(SList, data);
// SList->pList[SList->size] = data;
// SList->size++;
//}
//
//void PushHeadSeqList(SeqList *SList, SLDataType data)
//{
// assert(SList);
// SeqListAppend(SList, data);
// for (int i = SList->size - 1; i >= 0; i--)
// {
// SList->pList[i + 1] = SList->pList[i];
// }
// SList->pList[0] = data;
// SList->size++;
//}
void InsertSeqList(SeqList *SList, SLDataType data, int pos)
{
assert(SList);
assert(SList->pList);
SeqListAppend(SList, data);
if (pos < 0 || pos > SList->size)
return;
for (int i = SList->size - 1; i >= pos; i--)
{
SList->pList[i + 1] = SList->pList[i];
}
SList->pList[pos] = data;
SList->size++;
}
//复用InsertSeqList()函数
void PushBackSeqList(SeqList *SList, SLDataType data)
{
InsertSeqList(SList, data, SList->size);
}
void PushHeadSeqList(SeqList *SList, SLDataType data)
{
InsertSeqList(SList, data, 0);
}
bool CheckEmptySeqList(SeqList *SList)//为空返回true
{
assert(SList);
if (SList->size == 0)
return true;
else
return false;
}
void DeleteMiddleSeqList(SeqList *SList, int pos)
{
assert(SList);
assert(SList->pList);
if (CheckEmptySeqList(SList)||pos < 0 || pos >= SList->size)
return;
if (pos == SList->size - 1)
{
SList->size--;
}
else
{
for (int i = pos + 1; i < SList->size; i++)
{
SList->pList[i-1] = SList->pList[i ];
}
SList->size--;
}
}
//void DeleteBackSeqList(SeqList *SList)
//{
// assert(SList);
// assert(SList->pList);
// if (CheckEmptySeqList(SList))
// return;
// SList->size--;
//}
//void DeleteHeadSeqList(SeqList *SList)
//{
// assert(SList);
// assert(SList->pList);
// if (CheckEmptySeqList(SList))
// return;
// for (int i = 1; i < SList->size; i++)
// {
// SList->pList[i - 1] = SList->pList[i];
// }
// SList->size--;
//}
//复用DeleteMiddleSeqLis()函数
void DeleteBackSeqList(SeqList *SList)
{
DeleteMiddleSeqList(SList, SList->size-1);
}
void DeleteHeadSeqList(SeqList *SList)
{
DeleteMiddleSeqList(SList, 0);
}
static int findpos(SeqList *SList, SLDataType data)
{
assert(SList);
assert(SList->pList);
if (CheckEmptySeqList(SList))
return -1;
for (int i = 0; i < SList->size; i++)
{
if (SList->pList[i] == data)
return i;
}
return -1;
}
void FindSeqList(SeqList *SList, SLDataType data)
{
int tmp = findpos(SList, data);
if (tmp != -1)
printf("%d的坐标为:%d\\n", data,tmp);
else
printf("%d不存在\\n",data);
}
void ModifySeqList(SeqList *SList, SLDataType data, int pos)
{
assert(SList);
assert(SList->pList);
if (CheckEmptySeqList(SList) || pos < 0 || pos >= SList->size)
return;
SList->pList[pos] = data;
}
void DestroySeqList(SeqList *SList)
{
assert(SList);
assert(SList->pList);
free(SList->pList);
SList->pList = NULL;
SList->size = 0;
SList->capacity = 0;
}
Test.c中
#include"SeqList.h"
void SeqList1()
{
SeqList List1;
InitSeqList(&List1);
printf("尾插:\\n");
PushBackSeqList(&List1, 1);
PushBackSeqList(&List1, 2);
PushBackSeqList(&List1, 3);
PushBackSeqList(&List1, 4);
PushBackSeqList(&List1, 5);
PushBackSeqList(&List1, 6);
PrintSeqList(&List1);
printf("头插:\\n");
PushHeadSeqList(&List1, 6);
PushHeadSeqList(&List1, 5);
PushHeadSeqList(&List1, 4);
PushHeadSeqList(&List1, 3);
PushHeadSeqList(&List1, 2);
PushHeadSeqList(&List1, 1);
PrintSeqList(&List1);
printf("中间插:\\n");
InsertSeqList(&List1, 9, 0);
InsertSeqList(&List1, 9, 3);
InsertSeqList(&List1, 9, List1.size);
PrintSeqList(&List1);
printf("尾删:\\n");
DeleteBackSeqList(&List1);
DeleteBackSeqList(&List1);
DeleteBackSeqList(&List1);
PrintSeqList(&List1);
printf("头删:\\n");
DeleteHeadSeqList(&List1);
DeleteHeadSeqList(&List1);
DeleteHeadSeqList(&List1);
PrintSeqList(&List1);
printf("任意删:\\n");
DeleteMiddleSeqList(&List1, 2);
DeleteMiddleSeqList(&List1, 0);
DeleteMiddleSeqList(&List1, 3);
DeleteMiddleSeqList(&List1, 8);
PrintSeqList(&List1);
printf("寻找元素坐标:\\n");
PrintSeqList(&List1);
FindSeqList(&List1, 3);
FindSeqList(&List1, 30);
printf("修改首元素的值:\\n");
ModifySeqList(&List1, 30, 0);
PrintSeqList(&List1);
printf("销毁:\\n");
DestroySeqList(&List1);
}
int main()
{
SeqList1();
return 0;
}
链表
链表的概念:
链表属于线性表,因此其逻辑结构必定是连续的,链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表的优点:
1、空间按需所取,不会浪费空间
2、往头部插入不需要挪动数据
链表的缺点:
不支持随机存取
链表的种类:
单向链表、单向循环链表、带头结点的单向链表、带头结点的单向循环链表
双向链表、双向循环链表、带头结点的双向链表、带头结点的双向循环链表
头结点:
和正常的结点的创建、销毁、使用一样,但是头结点的数据域不存有效数据,即一般不访问头结点的数据域
以上八种结构最常用的为无头单向非循环链表以及带头双向循环链表
说明:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
单链表的实现:
首先说明一下实现的大致思路:
第一步:创建单链表
第二步:单链表的接口函数:增、删、查、改
第三步:单链表的销毁
建立一个工程,并创建下面三个文件,其中
SList.h里包含了所需要的头文件,各种函数的声明,以及单链表的创建 ,
SList.c包含了单链表的接口函数的定义,
Test..c则是对各个函数的功能检测
如图
SList.h中
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
//单向链表(不带头、非循环)的创建
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;//数据域
struct SListNode* next;//指针域
}SLNode;
//新建一个结点,该函数用户不可调用
static void CreatNode(SLNode**ppSList, SListDataType data);
void SListPushBack(SLNode**ppSList, SListDataType data);//尾插一个结点
void SListPushHead(SLNode**ppSList, SListDataType data);//头插一个结点
void SListPrint(SLNode**pSList);//打印单链表
void SListPobBack(SLNode**ppSList);//尾删
void SListPobHead(SLNode**ppSList);//头删
//这里还是使用了二级指针,是为了保证大部分函数的接口都是一致的
SLNode* SListFind( SLNode**ppSList, SListDataType data);//查找
void SListPushPosAfter(SLNode* pos, SListDataType data);//在pos结点之后插入新结点
//在pos结点之前插入新结点
void SListPushPosBefore(SLNode**ppSList, SLNode* pos, SListDataType data);
void SListDestroy(SLNode**PPSList);//销毁链表
Test.c中
#include"SList.h"
void TestSList()
{
SLNode *pSList = NULL;//pSList为头指针:指向第一个结点的指针叫头指针
//第一个结点可能为头结点或者是不带头结点的链表的第一个结点
//此时pSList为空指针,说明该链表一个结点都没有
printf("尾插:\\n");
SListPushBack(&pSList, 1);
SListPushBack(&pSList, 2);
SListPushBack(&pSList, 3);
SListPushBack(&pSList, 4);
SListPrint(&pSList);
printf("头插:\\n");
SListPushHead(&pSList, 1);
SListPushHead(&pSList, 2);
SListPushHead(&pSList, 3);
SListPushHead(&pSList, 4);
SListPrint(&pSList);
printf("尾删:\\n");
SListPobBack(&pSList);
SListPrint(&pSList);
printf("头删:\\n");
SListPobHead(&pSList);
SListPrint(&pSList);
printf("在pos之后插入结点:\\n");
SLNode* pos = SListFind(&pSList, 1);
SListPushPosAfter(pos, 9);
SListPrint(&pSList);
printf("在pos之前插入结点:\\n");
SLNode * pos1 = SListFind(&pSList, 3);
SListPushPosBefore(&pSList,pos1, 9);
SListPrint(&pSList);
printf("销毁链表:\\n");
SListDestroy(&pSList);
}
int main()
{
TestSList();
return 0;
}
SList.c中
#include"SList.h"
static void CreatNode(SLNode**ppSList, SListDataType data)
{
*ppSList = (SLNode*)malloc(sizeof(SLNode));
(*ppSList)->next = NULL;
(*ppSList)->data = data;
}
void SListPushBack(SLNode**ppSList, SListDataType data)
{
if (*ppSList == NULL)
{
CreatNode(ppSList, data);//传的其实是ppSList的拷贝
}
else
{
SLNode *tail = *ppSList;
while (tail->next != NULL)
{
tail = tail->next;
}
CreatNode(&(tail->next), data);
}
}
void SListPushHead(SLNode**ppSList, SListDataType data)
{
if (*ppSList == NULL)
{
CreatNode(ppSList, data);
}
else
{
SLNode *prev = NULL;
CreatNode(&prev, data);
prev->next = *ppSList;
*ppSList = prev;//把头指针移到第一个结点处
}
}
void SListPrint( SLNode**ppSList)
{
if (*ppSList == NULL)
printf("空表\\n");
else
{
SLNode*cur = *ppSList;
do
{
printf("%d", cur->data);
printf("->");
cur = cur->next;
} while (cur != NULL);
printf("\\n");
}
}
void SListPobBack(SLNode**ppSList)
{
if (*ppSList == NULL)
{
return;
}
else
{
SLNode *tail = *ppSList;
SLNode *before_tail = NULL;
while (tail->next != NULL)
{
before_tail = tail;
tail = tail->next;
}
free(tail);//释放掉要删除的结点的内存空间
before_tail->next = NULL;//将倒数第二结点的next置为空
}
}
void SListPobHead(SLNode**ppSList)
{
if (*ppSList == NULL)
{
return;
}
else
{
SLNode *cur = *ppSList;
*ppSList = (*ppSList)->next;//先将头指针指向第二个结点
free(cur);//释放第一个结点
}
}
SLNode* SListFind( SLNode**ppSList, SListDataType data)
{
SLNode*cur = *ppSList;
while (cur != NULL)
{
if (cur->data == data)
return cur;
cur = cur->next;
}
return NULL;
}
void SListPushPosAfter( SLNode* pos, SListDataType data)
{
if (pos == NULL)
{
printf("pos为空指针\\n");
return;
}
SLNode *cur = pos->next;
CreatNode(&(pos->next),data);
pos->next->next = cur;
}
void SListPushPosBefore(SLNode**ppSList, SLNode* pos, SListDataType data)
{
if (pos == NULL)
{
printf("pos为空指针\\n");
return;
}
if (*ppSList == NULL)
{
printf("链表为空\\n");
return;
}
SLNode *cur = *ppSList;
if (pos == cur)
{
CreatNode(&cur, data);
cur->next = *ppSList;
*ppSList = cur;
}
else
{
while (cur->next)
{
if (cur->next == pos)
break;
cur = cur->next;
}
if (cur->next == NULL)
{
printf("没有该结点\\n");
return;
}
SListPushPosAfter(cur,data);
}
}
void SListDestroy(SLNode**PPSList)
{
assert(*PPSList);
SLNode*cur = *PPSList;
SLNode*next = cur->next;
while (cur)
{
free(cur);
cur = next;
if(next!=NULL)
next = next->next;
}
*PPSList = NULL;
}
带头结点的循环双链表的实现
首先说明一下实现的大致思路:
第一步:创建链表
第二步:链表的初始化:初始化的目的是创建头结点
第三步:链表的接口函数:增、删、查、改
第四步:链表的销毁
同上,首先创建工程,并建立三个文件
SList.h里包含了所需要的头文件,各种函数的声明,以及链表的创建 ,
SList.c包含了链表的接口函数的定义
Test..c则是对各个函数的功能检测
SList.h中
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
typedef int SListDataType;
typedef struct SListNode
{
struct SListNode *prev;
struct SListNode *next;
SListDataType data;
}SLNode;
SLNode* SListInit();//初始化,(创建头结点)
static void CreateNode(SLNode**ppNew, SListDataType data);//创建新结点
void SListPushBack(SLNode*pHead, SListDataType data);//尾插
void SListPushHead(SLNode*pHead, SListDataType data);//头插
void SListPrint(SLNode*pHead);//打印
void SListPopHead(SLNode*pHead);//头删
void SListPopBack(SLNode*pHead);//尾删
SLNode* SListFind(SLNode*pList, SListDataType data);//查找
void SListInsertBefore(SLNode*pos, SListDataType data);//在pos结点后插入要给新节点
void SListErase(SLNode*pos);//删除pos结点
void SListDestroy(SLNode**ppHead);//销毁链表
Test.c中
#include"SList.h"
void TestSList()
{
SLNode *pList = SListInit();
printf("尾插:\\n");
SListPushBack(pList, 1);
SListPushBack(pList, 2);
SListPushBack(pList, 3);
SListPrint(pList);
printf("头插:\\n");
SListPushHead(pList, 1);
SListPushHead(pList, 2);
SListPushHead(pList, 3);
SListPrint(pList);
printf("头删:\\n");
SListPopHead(pList);
SListPrint(pList);
printf("尾删:\\n");
SListPopBack(pList);
SListPrint(pList);
printf("在值为2的结点之前插:\\n");
SListInsertBefore(SListFind(pList, 2), 9);
SListPrint(pList);
printf("销毁新插入的结点:\\n");
SListErase(SListFind(pList, 9));
SListPrint(pList);
printf("销毁链表:\\n");
SListDestroy(&pList);
if (pList == NULL)
printf("链表已经被销毁:\\n");
}
int main()
{
TestSList();
return 0;
}
SList.c中
#include"SList.h"
SLNode* SListInit()
{
SLNode*pHead = (SLNode*)malloc(sizeof(SLNode));
pHead->data = 0;
pHead->next = pHead;
pHead->prev = pHead;
return pHead;
}
static void CreateNode(SLNode**ppNew, SListDataType data)
{
*ppNew = (SLNode*)malloc(sizeof(SLNode));
(*ppNew)->data = data;
(*ppNew)->next = *ppNew;
(*ppNew)->prev = *ppNew;
}
//用SListInsertBefore( SLNode*pos, SListDataType data)对下列函数复用
//void SListPushBack(SLNode*pHead, SListDataType data)
//{
// assert(pHead);
// SLNode*cur = (pHead)->prev;
// CreateNode(&cur->next, data);
// cur->next->prev = cur;
// cur->next->next = pHead;
// (pHead)->prev = cur->next;
//
//}
//void SListPushHead(SLNode*pHead, SListDataType data)
//{
// assert(pHead);
// SLNode*tmp = pHead->next;
// CreateNode(&pHead->next,data);
// pHead->next->prev = pHead;
// pHead->next->next = tmp;
// tmp->prev = pHead->next;
//}
void SListPushBack(SLNode*pHead, SListDataType data)
{
SListInsertBefore(pHead, data);
}
void SListPushHead(SLNode*pHead, SListDataType data)
{
assert(pHead);
SListInsertBefore(pHead->next, data);
}
void SListPrint(SLNode*pHead)
{
assert(pHead);
if (pHead->next ==pHead)
return;
else
{
SLNode*cur = pHead->next;
do
{
printf("%d->", cur->data);
cur = cur->next;
} while (cur != pHead);
}
printf("\\n");
}
//用void SListErase(SLNode*pos)进行复用
//void SListPopHead(SLNode*pHead)
//{
// assert(pHead);
// if (pHead->next == pHead)
// return;
// else
// {
// SLNode*tmp = pHead->next;
// pHead->next->next->prev = pHead;
// pHead->next =tmp->next;
// free(tmp);
// }
//}
//void SListPopBack(SLNode*pHead)
//{
// assert(pHead);
// if (pHead->next == pHead)
// return;
// else
// {
// SLNode*tmp = (pHead)->prev;
// (pHead)->prev->prev->next = pHead;
// (pHead)->prev = (pHead)->prev->prev;
// free(tmp);
// }
//}
void SListPopHead(SLNode*pHead)
{
assert(pHead);
SListErase(pHead->next);
}
void SListPopBack(SLNode*pHead)
{
assert(pHead);
SListErase(pHead->prev);
}
SLNode* SListFind(SLNode*pHead, SListDataType data)
{
assert(pHead);
if (pHead->next == pHead)
return NULL;
else
{
SLNode*cur = pHead->next;
do
{
if (cur->data == data)
return cur;
cur = cur->next;
} while (cur !=pHead);
}
return NULL;
}
void SListInsertBefore( SLNode*pos, SListDataType data)
{
assert(pos);
SLNode*tmp = pos->prev;
CreateNode(&tmp->next, data);
tmp->next->prev = tmp;
tmp->next->next = pos;
pos->prev = tmp->next;
}
void SListErase(SLNode*pos)
{
assert(pos);
if (pos == pos->next)//如果pos为头结点就不用删了
return;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
void SListDestroy(SLNode**ppHead)
{
assert(*ppHead);
SLNode *cur = (*ppHead)->next;
SLNode* next = cur->next;
while (cur != ppHead)
{
free(cur);
cur = next;
if (next == *ppHead)
{
free(*ppHead);
*ppHead = NULL;
return;
}
next = next->next;
}
}
以上是关于2)数据结构之线性表的主要内容,如果未能解决你的问题,请参考以下文章