线性表之顺序表及链表

        线性表是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串……

        但值得注意的是线性表只是表示在逻辑上是线性结构,也就是说可以连成⼀条线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

        而在这里我们讲的是两个常见的线性表:顺序表和链表。

顺序表

        线性表的顺序存储结构是把线性表中所有元素按照其逻辑顺序依次存储到计算机开辟出的一段连续的存储空间中,而由于其元素不仅在逻辑结构上相邻,而且在物理结构(即实际存储位置)上也相邻,那么这时我们就将这种顺序存储结构称之为顺序表,也就是我们常说的数组。

        那么说到数组,我们就可以想到其长度,而其长度也分为固定与不固定两种,即定长数组与变长数组。那么这时我们就可以据此将顺序表分为静态顺序表与动态顺序表。

静态顺序表定义:

typedef int SLDataType;
#define N 7           //声明数组长度
typedef struct SeqList
{
    SLDataType a[N];  //定长数组
    int size;        //有效数据个数
}SL;

不难看出对于静态顺序表而言,当有效数据个数过少时,我们会造成大量的空间浪费,而当有效数据个数过多超出我们所声明的数组最大长度时,我们的空间又不够用,所以一般在实际生活中我们一般采用动态顺序表,下面我们也只讲动态顺序表,为了方便我们就直接将之称为顺序表。

实现

声明类型及顺序表结构
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int size;      //有效数据个数
	int capacity;  //空间容量大小
}SL;

对于所有数据结构而言,因为这里我们不确定到底要使用何种数据类型,以int类型为例,我们使用typedef将之重命名,以便于当我们要使用不同数据类型时的更改。

相关函数及实现

对于所有数据结构而言,我们要实现的其实不外乎增删查改四个操作,而对于顺序表而言,因为其随机存取的特性,所以这里对于其增删操作的方法较多。下面的相关函数实现。

头文件:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int DataType;

typedef struct SeqList
{
	DataType* arr;
	int size;
	int capacity;
}SL;

//初始化顺序表及销毁和打印
void SLInit(SL* sl);
void SLDestroy(SL* sl);
void SLPrint(SL* sl);

//扩容
void SLCheckCapacity(SL* ps);

//尾插法和头插法
void SLPushback(SL* sl, DataType e);
void SLPushfront(SL* sl, DataType e);

//尾删法和头删法
void SLPopback(SL* sl);
void SLPopfront(SL* sl);

//指定位置之前插入/删除数据
void SLInsert(SL* sl, int pos, DataType x);
void SLErase(SL* sl, int pos);

//查找
int SLFind(SL* sl, DataType e);

函数实现: 

#define _CRT_SECURE_NO_WARNINGS 1
#include"SL.h"

//初始化顺序表
void SLInit(SL* sl)
{
	sl->arr = NULL;   //数组初始化为空
	sl->size = sl->capacity = 0; //未插入元素,有效数据为0,容量为0
}

//删除顺序表
void SLDestroy(SL* sl)
{
	assert(sl);
	if (sl->arr)
	{
		free(sl->arr);
	}
	sl->arr = NULL;
	sl->size = sl->capacity = 0;
}

//打印顺序表
void SLPrint(SL* sl)
{
	for (int i = 0; i < sl->size; i++)
	{
		printf("%d ", sl->arr[i]);
	}
	printf("\n");
}

//扩容函数
void SLCheckCapacity(SL* sl)
{
	if (sl->size == sl->capacity)
	{
        //使用三目操作符,当容量为0时就直接令其等于4,否则扩大两倍
        //这样操作的目的是使空间不造成太多的浪费
		int newcapacity = sl->capacity == 0 ? 4 : 2 * sl->capacity;
        //定义一个指针,防止扩容失败造成我们本来的数据丢失
		DataType* tmp = (DataType*)realloc(sl->arr, newcapacity * sizeof(DataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			return;
		}
        //将扩容后的数组首地址赋值给原数组
		sl->arr = tmp;
        //扩容后的容量赋值给原容量
		sl->capacity = newcapacity;
	}
}


//尾插法和头插法
void SLPushback(SL* sl, DataType e)
{
	assert(sl);
	SLCheckCapacity(sl);
	sl->arr[sl->size++] = e;
}
void SLPushfront(SL* sl, DataType e)
{
	assert(sl);
	SLCheckCapacity(sl);
	for (int i = sl->size; i > 0; i--)
	{
		sl->arr[i] = sl->arr[i - 1];
	}
	sl->arr[0] = e;
	sl->size++;
}

//尾删法和头删法
void SLPopback(SL* sl)
{
	assert(sl);
	assert(sl->size);
	sl->size--;
}

void SLPopfront(SL* sl)
{
	assert(sl);
	assert(sl->size);
	for (int i = 0; i < sl->size - 1; i++)
	{
		sl->arr[i] = sl->arr[i + 1];
	}
	sl->size--;
}

//指定位置之前插入/删除数据
void SLInsert(SL* sl, int pos, DataType x)
{
	assert(sl);
	SLCheckCapacity(sl);
	for (int i = sl->size; i > pos; i--)
	{
		sl->arr[i] = sl->arr[i - 1];
	}
	sl->arr[pos] = x;
	sl->size++;
}
void SLErase(SL* sl, int pos)
{
	assert(sl);
	assert(pos >= 0 && pos < sl->size);
	for (int i = pos; i < sl->size - 1; i++)
	{
		sl->arr[i] = sl->arr[i + 1];
	}
	sl->size--;
}

//查找函数
int SLFind(SL* sl, DataType e)
{
	assert(sl);
	int flag = 0;
	for (int i = 0; i < sl->size; i++)
	{
		if (sl->arr[i] == e)
		{
			flag = 1;
			printf("找到了,在下标为%d的位置\n", i);
			return 0;
		}
	}
	if (flag == 0)
	{
		printf("没有找到!\n");
		return -1;
	}
}

链表

链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。其每个节点不仅包含我们的数据域,还包含下一个节点地址的指针域,如下图所示。你可以把它想象成火车,一个车厢接着一个车厢。

而与顺序表不同,其优点是在进行增删操作时我们只需要修改相关节点的指针域即可,方便省时。但其缺点也很明显,就是其不具有随机存取特性,所以我们不能根据下标直接访问其中的元素,而只能根据指针一步步访问过去。

实现

声明类型及链表结构
typedef int DataType;
typedef struct SLTNode
{
	DataType data;     //数据域
	struct SLTNode* next;  //下一个节点的指针
}SLTNode;
相关函数及实现

对于链表而言我们也只需要进行增删查改操作,下面是函数声明及定义。

头文件:

#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>

typedef int DataType;
typedef struct SLTNode
{
	DataType data;
	struct SLTNode* next;
}SLTNode;

//打印函数及创建链表
void SLTPrint(SLTNode* phead);
SLTNode* CreateNode(DataType x);

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, DataType x);
void SLTPushFront(SLTNode** pphead, DataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode** pphead, DataType x);

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, DataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, DataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDesTroy(SLTNode **pphead); 

在上述声明中,我们不难看出其中多次使用二级指针,其原因是因为,我们需要对其节点进行操作,而我们的节点是指针,所以如果想对节点进行操作,我们就需要传递二级指针,这就好比是我们需要一个函数来交换两个整型数据a和b的值一样,我们不能直接传递a和b值,而是传递a,b的指针,即int*。

函数实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SLTNode.h"
//打印
void SLTPrint(SLTNode* phead)
{
	assert(phead);
	SLTNode* pcur = phead;
    //为NULL就停止
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

//创建节点,多个函数需要创建节点,所以封装为函数,方便调用
SLTNode* CreateNode(DataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//尾/头插
void SLTPushBack(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* newnode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	SLTNode* ptail = *pphead;
    //遍历找到尾节点
	while (ptail->next)
	{
		ptail = ptail->next;
	}
    //插入
	ptail->next = newnode;
}
void SLTPushFront(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* newnode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾/头删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* ptail = *pphead;
	SLTNode* prev = NULL;
    //遍历找到尾节点
	while (ptail->next)
	{
		prev = ptail;
		ptail = ptail->next;
	}
	free(ptail);
	ptail = NULL;
	prev->next = NULL;
}
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* ptail = (*pphead)->next;
	free(*pphead);
	*pphead = ptail;
}

//查找
SLTNode* SLTFind(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, DataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
		return;
	}
	SLTNode* prev = *pphead;
	SLTNode* newnode = CreateNode(x);
    //遍历找到插入节点位置
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = newnode;
	newnode->next = pos;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
		return;
	}
	SLTNode* prev = *pphead;
    //遍历找到删除节点位置
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, DataType x)
{
	assert(pos);
	SLTNode* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
    //创建节点来保存删除节点的next节点
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* pcur = *pphead;
    //遍历链表删除所有节点
	while (pcur)
	{
        //创建节点来保存删除节点的next节点
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = NULL;
	}
	*pphead = NULL;
}

链表的结构⾮常多样,以下情况组合起来就有8种(2x2x2)链表结构:

其结构如下 

 

而上面我们讲的是单向不带头不循环链表 ,即单链表,虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构:单链表和双向带头循环链表,下面给出双向带头循环链表的代码实现,其余类型的链表可自行实现。

头文件:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


typedef int Typedata;
typedef struct List
{
	Typedata data;
	struct List* next;
	struct List* prev;
}List;


//初始化、打印、销毁
List* Inithead(void);
void PrintList(List* phead);
void Destroy(List* phead);


//尾\头插
void Pushback(List* phead,Typedata x);
void Pushfront(List* phead,Typedata x);


//尾\头删
void Popback(List* phead);
void Popfront(List* phead);


//查找
List* Find(List* phead,Typedata x);


//在pos位置之后插入
void Insert(List* pos, Typedata x);
void DelInsert(List* pos);

函数实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"


//创建节点
List* CreateNode(Typedata x)
{
	List* newnode = (List*)malloc(sizeof(List));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;
	return newnode;
}


//初始化哨兵位
List* Inithead(void)
{
	List *phead = CreateNode(-1);
	return phead;
}


//打印
void PrintList(List* phead)
{
	assert(phead);
	List* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}


//销毁
void Destroy(List* phead)
{
	List* pcur = phead->next;
	while (pcur != phead)
	{
		List* del = pcur;
		pcur = pcur->next;
		free(del);
	}
}


//尾\头插
void Pushback(List* phead, Typedata x)
{
	List* newnode = CreateNode(x);
	newnode->next = phead;
	newnode->prev = phead->next;
	phead->prev->next = newnode;
	phead->prev = newnode;
}
void Pushfront(List* phead, Typedata x)
{
	List* newnode = CreateNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next = newnode;
	phead->next->prev = newnode;
}


//尾\头删
void Popback(List* phead)
{
	assert(phead->next != phead);
	List* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}
void Popfront(List* phead)
{
	assert(phead->next != phead);
	List* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}


//查找
List* Find(List* phead, Typedata x)
{
	assert(phead);
	List* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}


//在pos位置之后插入
void Insert(List* pos, Typedata x)
{
	assert(pos);
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}
void DelInsert(List* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值