数据结构基础--线性表

线性表:是n个具有相同特性的数据元素的有序排列

一、顺序表

顺序表:用一段物理地址连续的存储单元一次存储数据元素的线性结构,一般情况下采用数组存储

顺序表头文件

#pragma once
#include<stdio.h>
#include<assert.h>
//void assert( int expression );
//assert 的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。
//使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
#include<stdlib.h>//包含malloc,calloc与free
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
	SLDataType* a;
	size_t size;
	size_t capacity;//容量,最大数组存储量,为数据个数
}SL;
//动态顺序表的缺陷:
//1.如果空间不够,增容会有一定的性能消耗,可能会有空间浪费
//2.在头部或者中部的插入删除效率低,为O(n)


//静态顺序表,数组给少不够用,给多会浪费
//#define N 1000
//typedef int SLDataType;
//typedef struct SeqList
//{
//	SLDataType array[N];
//	int size;
//}SL;


//接口函数
void SeqListInit(SL* ps);//初始化顺序表
void SeqListDestory(SL* ps);//销毁顺序表
void CheckCapacity(SL* ps);
void SeqListPushBack(SL* ps, SLDataType x);//尾插法
void SeqListPopBack(SL* ps);//尾删
void SeqListPushFront(SL* ps, SLDataType x);//头插
void SeqListPopFront(SL* ps);//头删
int SeqListFind(const SL* ps, SLDataType x);//查找指定值
void SeqListPrint(const SL* ps);//打印顺序表
void SeqListInsert(SL* ps, size_t pos,SLDataType x);//在pos未知插入数据x
void SeqListErase(SL* ps, size_t pos);//删除pos位置元素
void SeqListSize(const SL* ps);//查看顺序表元素个数
void SeqListAt(SL*, size_t pos, SLDataType x);//修改pos位置数据为x

顺序表源文件:

#include"SeqList.h"
//接口函数的实现

//初始化顺序表
void SeqListInit(SL *ps)
{
	ps->a = NULL;//初始顺序表为空
	ps->size = 0;//初始数据个数为0
	ps->capacity = 0;//初始空间容量为0
}
//销毁顺序表
void SeqListDestory(SL* ps)
{
	assert(ps != NULL);
	free(ps->a);//释放动态开辟空间
	ps->a = NULL;//置空
	ps->size = 0;
	ps->capacity = 0;
}
//检查顺序表容量是否满,增容
//为什么不采取插一个数据,增容一个空间的方式呢,因为这样也太麻烦了,代价也太大了
//一般情况下,为了避免频繁的增容,当空间满了后,我们不会一个一个的去增,而是一次增容 2 倍,当然也不会一次增容太大,比如 3 倍 4 倍,空间可能会浪费,2 倍是一个折中的选择。
void CheckCapacity(SL*ps)
{
	assert(ps!=NULL);
	if (ps->size==ps->capacity)
	{
		int newcapacity;//设置新容量
		if (ps->capacity==0)
		{
			newcapacity = ps->capacity = 4;//原容量为0,扩容为4
		}
		else
		{
			newcapacity = 2 * ps->capacity;//原容量不为0,扩容为原来二倍
		}
		SLDataType* p = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		//C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。
		//ptr -- 指针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
		//size -- 内存块的新的大小,以字节为单位。如果大小为 0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。
		//该函数返回一个指针 ,指向重新分配大小的内存。如果请求失败,则返回 NULL。
		if (p==NULL)
		{
			perror("realloc");
			exit(-1);
		}
		else
		{	
			ps->a = p;//p不为空,开辟成功
			ps->capacity = newcapacity;//更新容量
		}
	}
}

//顺序表尾插
void SeqListPushBack(SL* ps,SLDataType x)
{
	assert(ps != NULL);
	CheckCapacity(ps);//检查顺序表容量是否已满

	ps->a[ps->size] = x;
	ps->size++;
}

//顺序表尾删
void SeqListPopBack(SL*ps)
{
	assert(ps != NULL);
	assert(ps->size > 0);
	ps->size--;//已储存数据个数减一
	//不知道SLDataType是什么数据类型,不能贸然赋值为0
	//ps->a[ps->size-1]=0;
}

//顺序表头插
void SeqListPushFront(SL *ps,SLDataType x)
{
	assert(ps);
	CheckCapacity(ps);

	int i = 0;
	for (i=ps->size;i>=0;i--)//顺序表中[0,size-1]的元素依次向后挪动一位
	{
		ps->a[i + 1] = ps->a[i];
	}
	ps->a[0] = x;
	ps->size++;
}

//顺序表头删
void SeqListPopFront(SL* ps)
{
	assert(ps);  //断言
	assert(ps->size > 0);  //顺序表不能为空

	int i = 0;
	for (i = 1; i < ps->size; i++)  //顺序表中[1,size-1]的元素依次向前挪动一位
	{
		ps->a[i - 1] = ps->a[i];
	}
	ps->size--;  //有效数据个数-1
}

//打印顺序表
void SeqListPrint(const SL* ps)
{
	assert(ps != NULL);
	if (ps->size==0)
	{
		printf("顺序表为空\n");
		return;
	}
	
	for (int i = 0;i<ps->size;i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

//在顺序表中查找某值
int SeqListFind(const SL*ps,SLDataType x)
{
	assert(ps);

	for (int i=0;i<ps->size;i++)
	{
		if (ps->a[i] == x)
		{
			return i;//查找到,返回该值在数组中的下标
		}
	}
	return -1;//未找到
}

//在顺序表指定下标位置插入数据
// 
//int i = 0;
//for (i = ps->size - 1; i >= pos; i--)
//{
//	ps->a[i + 1] = ps->a[i];
//}
//这种写法,当顺序表为空size=0时,i=-1,执行i>=pos时,i会被算数转化成无符号数
//无符号数-1为一个很大值的正数,远大于pos,满足条件进入循环,会造成越界访问
void SeqListInsert(SL* ps, size_t pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//检查pos下标合法性
	CheckCapacity(ps);
	int i = 0;
	for (i=ps->size;i>pos;i--)//将pos位置后面的数据依次向后移位
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}

//顺序表中删除指定下标位置数据
void SeqListErase(SL* ps,size_t pos)
{
	assert(ps);
	assert(ps->size > 0);
	assert(pos >= 0 && pos < ps->size);

	int i = 0;
	for (i = pos + 1; i < ps->size; i++)//将pos位置后面的数据一次向前移位
	{
		ps->a[i - 1] = ps->a[i];
	}
	ps->size--;
}

//查看顺序表中有效数据的个数
void SeqListSize(SL* ps)
{
	assert(ps);
	return ps->size;
}
//在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问,要去调用它的函数来访问和修改,这样更加规范安全,也更方便检查是否出现了越界等一些错误情况

//修改指定下标位置的数据
void SeqListAt(SL* ps, size_t pos, SLDataType x)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);
	assert(ps->size > 0);

	ps->a[pos] = x;
}

测试:

#include"SeqList.h"

void test01()
{
	SL s1;
	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	SeqListPopBack(&s1);//删除最后一个元素
	SeqListErase(&s1, 2);//删除数组第二个元素
	SeqListPushFront(&s1, 20 );
	SeqListPrint(&s1);
}

void test02()
{
	SL s1;
	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	SeqListAt(&s1,2,8);
	SeqListPrint(&s1);
}

int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}

 顺序表缺陷:

1.空间不足,需要扩容。扩容存在一定代价,其次还可能存在一定空间浪费

2.头部或者中部插入删除,需要挪动数据

二、单链表

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑结构是通过链表中的指针链接实现的
实际中链表的结构非常多样,以下情况组合起来有8种链表结构:
(1)单向、双向(是否支持向前访问)
(2)带头、不带头(在链表开头是否有一个不存放数据的头结点)
(3)循环、非循环(是否能通过链表的尾结点直接访问到链表的第一个结点)

单链表头文件:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int DataType;

//不带头单向非循环链表
typedef struct SListNode//单链表结点结构类型
{
	DataType data;//结点数据域
	struct SListNode* next;
	//结点指针域,指针指向下一个结点,而下一个结点的类型也为SListNode
}SLTNode;

//接口函数
void SListPrint(SLTNode* plist);//显示
void SListPushBack(SLTNode** pplist,DataType x);//尾插
void SListPushFront(SLTNode** pplist, DataType x);//头插
void SListPopBack(SLTNode** pplist);//尾删
void SListPopFront(SLTNode** pplist);//头删
//insert与erase的使用以find为前提,通过find找到插入位置即pos
SLTNode* SListFind(SLTNode* plist, DataType x);//查找
void SListInsert(SLTNode** pplist,SLTNode* pos, DataType x);//在pos位置前插入x
void SListErase(SLTNode** pplist, SLTNode* pos);//删除pos位置的值

单链表源文件:

#include"SingleList.h"
//接口函数实现

void SListPrint(SLTNode* plist)
{
	SLTNode* cur = plist;
	while (cur!=NULL)//链表的最后一个指针域为NULL
	{
		printf("%d ",cur->data);//输出结点数据域
		cur = cur->next;//通过指针域走向下一个元素
	}
	printf("\n");
}

void SListPushBack(SLTNode** pplist,DataType x)//这里的pplist是plist的地址
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//开辟一块大小与SLTNode相同的空间
	newnode->data = x;//新结点数据域赋值
	newnode->next = NULL;//将新结点的指针域置空
	if (*pplist==NULL)//*pplist其实就是plist
	{
		*pplist = newnode;
		//如果传入形参为SLTNode*型,实参与形参类型相同,形参的改变不影响实参,形参是实参的一个拷贝
		//所以采用指针的方式传递,传入首结点地址,所以采用二级指针SLTNode**
	}
	else
	{
		SLTNode* tail = *pplist;//尾结点的指针需要从链表的第一个元素开始查找
	    while (tail->next!=NULL)
	    {
		tail = tail->next;
	    }
	    tail->next = newnode;//尾结点链接新结点
	}
}

void SListPushFront(SLTNode** pplist,DataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;

	newnode->next = *pplist;//插入结点指针域为原来首地址
	*pplist = newnode;
}

void SListPopFront(SLTNode** pplist)
{
	SLTNode* next= (*pplist)->next;//*与->为同一个优先级,所以加()
	free(*pplist);//释放链表首个结点
	*pplist = next;
}

void SListPopBack(SLTNode** pplist)
{
	//assert(pplist != NULL);
	if (*pplist == NULL)//当链表为空
	{
		return;
	}
	else if ((*pplist)->next==NULL)//当仅有一个结点
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SLTNode* tail = *pplist;
		SLTNode* prev = NULL;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}//prev为tail的前一个指针,当tail到链表的末尾时,prev指向倒数第二个结点
		free(tail);
		prev->next = NULL;
	}
}

SLTNode* SListFind(SLTNode* plist,DataType x)
{
	SLTNode* cur = plist;
	while (cur!=NULL)
	{
		if (cur->data==x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void SListInsert(SLTNode** pplist, SLTNode* pos, DataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	if (pos == *pplist)
	{
		SListPushFront(pplist,x);
	}
	else
	{
		SLTNode* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}


void SListErase(SLTNode** pplist, SLTNode* pos, DataType x)
{

	SLTNode* prev = *pplist;
	if (pos == *pplist)
	{
		SListPopFront(pplist);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

测试:

#include"SingleList.h"

void test01()
{
	SLTNode* plist = NULL;//plist为指向第一个元素的指针
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushFront(&plist, 0);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPrint(plist);
}


void test02()
{
	SLTNode* plist = NULL;//plist为指向第一个元素的指针
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);

	SLTNode* pos = SListFind(plist, 3);
	if (pos!=NULL)
	{
		SListInsert(&plist,pos,30);//在数字3前面插入一个30
	}

	SLTNode* pos1 = SListFind(plist, 1);
	if (pos1)
	{
		SListInsert(&plist, pos1, 10);
	}
	SListPrint(plist);
}

void test03()
{
	SLTNode* plist = NULL;//plist为指向第一个元素的指针
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);

	SLTNode* pos1 = SListFind(plist, 3);
	if (pos1)
	{
		SListErase(&plist, pos1);
	}

	SLTNode* pos2 = SListFind(plist, 1);
	if (pos2)
	{
		SListErase(&plist, pos2);
	}

	SListPrint(plist);
}

int main()
{
	//test01();
	//test02();
	test03();
	system("pause");
	return 0;
}

三、带头双向循环链表

哨兵节点,也是头结点,是一个 dummy node. 可以用来简化边界条件.

是一个附加的链表节点.该节点作为第一个节点,它的值域不存储任何东西.

只是为了操作的方便而引入的.

如果一个链表有哨兵节点的话,那么线性表的第一个元素应该是链表的第二个节点.

 双链表头文件:

#pragma once

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//带头双向循环链表
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;//后继指针域
	struct ListNode* prev;//前驱指针域
	LTDataType data;
}ListNode;

//接口函数
//void ListInit(ListNode* plist);//初始化
ListNode* ListInit();
void ListDestory(ListNode* plist);//销毁
void ListPushBack(ListNode* plist,LTDataType x);//尾插
void ListPushFront(ListNode* plist, LTDataType x);//头插
void ListPopBack(ListNode* plist);
void ListPopFront(ListNode* plist);
ListNode* ListFind(ListNode* plist,LTDataType x);
void ListInsert(ListNode *pos,LTDataType x);//在pos前插入x
void ListErase(ListNode* pos);//删除pos位置的值
void ListPrint(ListNode* plist);

双链表源文件:

//带头双向循环链表
//哨兵位:
//1.对于一个已经创建和初始化的链表来讲,可以没有数据,即头节点,尾节点等,但是不能没有哨兵位,因为哨兵位并不是帮我们存储数据的,只是来定位链表的。即使是一个空链表,它也会有哨兵位,所以我们可以用哨兵位是否为空来检测程序运行情况。
//2.对于链表来讲,一个很重要的地方就是头的位置,而哨兵位则可以起到定位头的作用,它的下一个节点就是头节点。
//3.在双向循环链表中,如果我们要遍历,那么很难去说明结束条件,而我们可以用哨兵位来作为结束条件,当当前节点为哨兵位时,遍历结束。

ListNode* CreateListNode(LTDataType x)//创建一个新的结点
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;

}

//采用传入参数的模式进行链表的初始化,创造头结点的时候
//考虑到形参与实参的关系,需要传入参数为二级指针
//void ListInit(ListNode** plist)
//{
//	*plist = CreateListNode(0);//创建一个新的结点为头节点,数据域为0
//	(*plist)->next = *plist;
//	(*plist)->prev = *plist;
//	//当只有一个头节点存在时,他的前驱结点指向自己,后继域也指向自己,从而实现单独头节点的循环
//}

ListNode* ListInit()
{
	//构造一个有哨兵位的空的单链表
	ListNode* plist = CreateListNode(0);
	plist->next = plist;
	plist->prev = plist;
	return plist;
}


void ListDestory(ListNode* plist)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur!=plist)//逐个释放链表内容
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(plist);
}

void ListPushBack(ListNode* plist,LTDataType x)
{
	assert(plist);
	ListNode* tail = plist->prev;//哨兵位的前驱指针指向尾结点
	ListNode* newnode = CreateListNode(x);//创建一个新的存放数据为x的结点
	//不论链表为空还是不为空都可以插入
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;
}

void ListPushFront(ListNode* plist,LTDataType x)
{
	//plist为哨兵位,始终再链表的最前端
	assert(plist);
	ListNode* first = plist->next;
	ListNode* newcode = CreateListNode(x);
	plist->next = newcode;
	newcode->prev = plist;
	newcode->next = first;
	first->prev = newcode;
	//plist->newcode->first,其中newcode1为头节点
}

void ListPopFront(ListNode* plist)
{
	assert(plist);
	assert(plist->next!=plist);
	ListNode* first = plist->next;
	ListNode* second = first->next;
	plist->next = second;
	second->prev = plist;
	free(first);
}

void ListPopBack(ListNode* plist)
{
	assert(plist);
	assert(plist->next!=plist);
	ListNode* tail = plist->prev;
	ListNode* beforetail = tail->prev;//尾结点的前一个
	plist->prev = beforetail;
	beforetail->next = plist;
	free(tail);
	tail = NULL;
}

ListNode* ListFind(ListNode* plist,LTDataType x)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur!=plist)
	{
		if (cur->data==x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void ListInsert(ListNode* pos,LTDataType x)
{
	assert(pos);
	ListNode* newcode = CreateListNode(x);
	ListNode* beforepos = pos->prev;
	newcode->next = pos;
	beforepos->next = newcode;
	newcode->prev = beforepos;
	pos->prev = newcode;
}

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* beforepos = pos->prev;
	ListNode* nextpos = pos->next;
	beforepos->next = nextpos;
	nextpos->prev = beforepos;
	free(pos);

}

void ListPrint(ListNode* plist)
{
	ListNode* cur = plist->next;//cur指向头节点
	while (cur != plist)//当指向哨兵位时结束打印
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

测试:

#include"list.h"

void test01()
{
	//ListNode* plist = NULL;
	//ListInit(plist);
	ListNode* plist = ListInit();
	ListPushBack(plist, 10);
	ListPushBack(plist, 9);
	ListPushBack(plist, 8);
	ListPushBack(plist, 7);
	ListPushFront(plist, 6);
	ListPushFront(plist, 5);
	ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopBack(plist);
	ListPrint(plist);
	ListFind(plist,6);
	ListDestory(plist);
}

void test02()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);
	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		printf("找到了\n");
	}
	else
	{
		printf("没找到\n");
	}
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值