(数据结构)顺序表和链表(C实现)

 

目录

1.线性表

2.顺序表

3.链表

4.顺序表和链表的区别与联系


  • 1.线性表

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

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组链式结构的形式存储。 

注意 : 
          逻辑结构 : 线性结构(连续的)
          物理结构(内存中) : 不一定是连续的 ( 如下面的得顺序表与链表都是线性表, 但顺序表是一段连续的内存空间,而链表则非连续 )


  • 2.顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储。
2. 动态顺序表:使用动态开辟的数组存储。

  • 静态顺序表

// 顺序表的静态存储
#define N 8
typedef int SLDataType;
typedef struct SeqList{
    SLDataType array[N]; // 定长数组
    size_t size; // 有效数据的个数
}SeqList;

 

静态顺序表在实际使用过程中因其长度固定, 不灵活, 我们不常用, 而动态顺序表才是我们常用的顺序表

  • 动态顺序表

typedef int SLDataType;
// 顺序表的动态存储
typedef struct SeqList{
    SLDataType* array; // 指向动态开辟的数组
    size_t size ; // 有效数据个数
    size_t capacity ; // 容量空间的大小
}SeqList;

 

  • 动态顺序表的接口实现

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多
了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下
面我们实现动态顺序表。

  •     SeqList.h进行声明
#pragma once
#include<stdio.h>
#include<stdlib.h.>
#include<assert.h>
//Sequence list
typedef int SLDataType;
typedef struct SeqList {
	SLDataType* array;
	size_t size;
	size_t capacity;
}SeqList;
void SeqListInit(SeqList* psl, size_t capacity);//初始化
void SeqListDestory(SeqList* psl);//销毁
void CheckCapacity(SeqList* psl);//扩容
void SeqListPushBack(SeqList* psl, SLDataType x);//尾插
void SeqListPopBack(SeqList* psl);//尾删
void SeqListPushFront(SeqList* psl, SLDataType x);//头插
void SeqListPopFront(SeqList* psl);//头删
int SeqListFind(SeqList* psl, SLDataType x);//查找
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);//在第pos个位置插入
void SeqListErase(SeqList* psl, size_t pos);//删除第pos个
void SeqListRemove(SeqList* psl, SLDataType x);//删除所有x
void SeqListModify(SeqList* psl, size_t pos, SLDataType x);//修改
void SeqListPrint(SeqList* psl);//输出
void SeqListBubbleSort(SeqList* psl);//冒泡排序
int SeqListBinaryFind(SeqList* psl, SLDataType x);//二分查找
void SeqListRemoveAll(SeqList* psl, SLDataType x);//在先排序的基础上,将x去重
  • SeqList.c定义函数
#include"seqlist.h"
void SeqListInit(SeqList* psl, size_t capacity) {//初始化
	psl->array = (SLDataType*)calloc(capacity, sizeof(SeqList));
	psl->size = 0;
	psl->capacity = capacity;
}
void SeqListDestory(SeqList* psl) {//销毁空间
	assert(psl);
	if (psl->array) {
		free(psl->array);
		psl->size = 0;
		psl->capacity = 0;
	}
}
void CheckCapacity(SeqList* psl) {//检查容量
	assert(psl);
	if (psl->size == psl->capacity) {
		psl->capacity *= 2;
		psl->array = (SLDataType*)realloc(psl->array, psl->capacity * sizeof(SLDataType));
	}
}
void SeqListPushBack(SeqList* psl, SLDataType x) {//尾插
	CheckCapacity(psl);
	psl->array[psl->size++] = x;
}
void SeqListPopBack(SeqList* psl) {//尾删
	assert(psl || psl->size);
	--psl->size;
}
void SeqListPushFront(SeqList* psl, SLDataType x) {//头插
	assert(psl);
	CheckCapacity(psl);
	for (int i = psl->size; i > 0; --i) {
		psl->array[i] = psl->array[i - 1];
	}
	++psl->size;
	psl->array[0] = x;
}
void SeqListPopFront(SeqList* psl) {//头删
	assert(psl || psl->size);
	for (int i = 1; i < (int)psl->size; ++i) {
		psl->array[i - 1] = psl->array[i];
	}
	--psl->size;
}
int SeqListFind(SeqList* psl, SLDataType x) {//查找
	for (int i = 0; i < (int)psl->size; ++i) {
		if (psl->array[i] == x) {
			return i;
		}
	}
	return -1;
}
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) {//在第pos个位置插入
	assert(psl || pos <= psl->size);
	for (int i = psl->size; i > (int)pos; --i) {
		psl->array[i] = psl->array[i - 1];
	}
	psl->array[pos] = x;
	++psl->size;
}
void SeqListErase(SeqList* psl, size_t pos) {//删除第pos个
	assert(psl);
	for (int i = pos - 1; i < (int)psl->size - 1; ++i) {
		psl->array[i] = psl->array[i + 1];
	}
	--psl->size;
}
void SeqListRemove(SeqList* psl, SLDataType x) {//删除所有x
	assert(psl);
	int i;
	while ((i = SeqListFind(psl, x)) != -1) {
		SeqListErase(psl, i + 1);
	}
}
void SeqListModify(SeqList* psl, size_t pos, SLDataType x) {//修改
	assert(psl || pos <= psl->size);
	psl->array[pos - 1] = x;
}
void SeqListPrint(SeqList* psl) {//输出
	for (int i = 0; i < (int)psl->size; ++i) {
		printf("%d  ", psl->array[i]);
	}
	putchar('\n');
}
void SeqListBubbleSort(SeqList* psl) {//冒泡排序
	SLDataType tmp;
	for (int i = 0; i < (int)psl->size; ++i) {
		for (int j = 0; j < (int)psl->size - 1; ++j) {
			if (psl->array[j] > psl->array[j + 1]) {
				tmp = psl->array[j];
				psl->array[j] = psl->array[j + 1];
				psl->array[j + 1] = tmp;
			}
		}
	}
}
int SeqListBinaryFind(SeqList* psl, SLDataType x) {//二分查找
	int left = 0;
	int right = psl->size - 1;
	int mid = 0;
	while (left <= right) {
		mid = (left + right) / 2;
		if (psl->array[mid] < x) {
			left = mid + 1;
		}
		else if (psl->array[mid] > x) {
			right = mid - 1;
		}
		else {
			return mid;
		}
	}
	return -1;
}
void SeqListRemoveAll(SeqList* psl, SLDataType x) {//在先排序的基础上,将x去重
	for (int i = 0; i < (int)psl->size; ) {
		if (psl->array[i] == x) {
			SeqListErase(psl, i + 1);
			continue;
		}
		else if (psl->array[i] > x) {
			return;
		}
		++i;
	}
}
  •  调试入口main.c
#include"seqlist.h"
int main() {
	SeqList test;
	SeqList* p = &test;
	SeqListInit(p,10);
	for (int i = 0; i < 12; ++i) {
		SeqListPushFront(p, i + 1);
	}
	SeqListPushFront(p, 0);
	SeqListPushFront(p, -1);
	SeqListPrint(p);
	printf("\n\n");
	SeqListPopBack(p);
	SeqListPopFront(p);
	SeqListPrint(p);
	printf("\n\n");
	printf("%d\n",p->array[SeqListFind(p, 5)]);
	SeqListInsert(p, 4, 4);
	SeqListInsert(p, 6, 4);
	SeqListErase(p, 2);
	SeqListPrint(p);
	printf("\n\n");
	SeqListRemove(p, 4);
	SeqListPrint(p);
	printf("\n\n");
	SeqListBubbleSort(p);
	SeqListPrint(p);
	printf("\n\n");
	printf("%d\n", p->array[SeqListBinaryFind(p, 5)]);
	SeqListInsert(p, 4, 4);
	SeqListInsert(p, 6, 4);
	SeqListInsert(p, 7, 4);
	SeqListBubbleSort(p);
	SeqListPrint(p);
	printf("\n\n");
	SeqListRemoveAll(p, 4);
	SeqListPrint(p);
	printf("\n\n");
	SeqListDestory(p);
	system("pause");
	return 0;
}
  • 顺序表的问题及思考

    问题:
在学习顺序表之前我们肯定对数组有了一定的了解, 数组中增删元素都是非常麻烦的操作, 顺序表也如此
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
   思考:
如何解决以上问题呢?下面来看看链表的结构。


  • 3.链表

链表的概念及结构:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链
接次序实现的 。

 

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向、双向

2. 带头、不带头


3. 循环、非循环

8种组合如下 :
单向带头非循环链表
单向带头循环链表
双向带头非循环链表
双向带头循环链表
单向不带头非循环链表
单向不带头循环链表
双向不带头非循环链表
双向不带头循环链表 

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结
构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向
循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而
简单了,后面代码实现了就知道了。

  • 链表的实现

  • 1、无头单向非循环链表增删查改实现

      aList.h进行声明

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType _data;
	struct SListNode* _next;
}SListNode;
typedef struct SList
{
	SListNode* _head;
}SList;
void SListInit(SList* plist);
void SListDestory(SList* plist);
SListNode* BuySListNode(SLTDataType x);
void SListPushFront(SList* plist, SLTDataType x);//头插
void SListPopFront(SList* plist);//头删
void SListPushBack(SList* plist, SLTDataType x);//尾插
void SListPopBack(SList* plist);//尾删
SListNode* SListFind(SList* plist, SLTDataType x);//查找
void SListInsertAfter(SListNode* pos, SLTDataType x);// 在pos的后面进行插入
void SListInsertFront(SList* plist, SListNode* pos, SLTDataType x);// 在pos的前面进行插入
void SListEraseAfter(SListNode* pos);//删除pos后面的结点
void SListRemove(SList* plist, SLTDataType x);
void SListPrint(SList* plist);

  alist.c定义函数 

#include "alist.h"
void SListInit(SList* plist) {
	assert(plist);
	plist->_head = NULL;
}
void SListDestory(SList* plist) {//销毁
	assert(plist);
	SListNode* tmp = plist->_head;
	for (SListNode* cur = tmp; tmp; cur = tmp) {
		tmp = cur->_next;
		free(cur);
	}
}
SListNode* BuySListNode(SLTDataType x) {

}
void SListPushFront(SList* plist, SLTDataType x) {//头插
	assert(plist);
	SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
	cur->_data = x;
	cur->_next = plist->_head;
	plist->_head = cur;
}
void SListPopFront(SList* plist) {//头删
	assert(plist);
	SListNode* cur = plist->_head;
	plist->_head = cur->_next;
	free(cur);
	cur = NULL;
}
void SListPushBack(SList* plist, SLTDataType x) {//尾插
	assert(plist);
	SListNode* p;
	for (p = plist->_head; p && p->_next; p = p->_next);
	SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
	cur->_data = x;
	p->_next = cur;
	cur->_next = NULL;
}
void SListPopBack(SList* plist) {//尾删
	assert(plist);
	SListNode* p;
	for (p = plist->_head; p && p->_next && p->_next->_next; p = p->_next);
	free(p->_next);
	p->_next = NULL;
}
SListNode* SListFind(SList* plist, SLTDataType x) {//查找
	assert(plist);
	for (SListNode* cur = plist->_head; cur; cur = cur->_next) {
		if (cur->_data == x) {
			return cur;
		}
	}
	return NULL;
}
void SListInsertAfter(SListNode* pos, SLTDataType x) {// 在pos的后面进行插入
	assert(pos);
	SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
	cur->_data = x;
	cur->_next = pos->_next;
	pos->_next = cur;
}
void SListInsertFront(SList* plist, SListNode* pos, SLTDataType x) {// 在pos的前面进行插入
#if 0
	assert(pos && plist);
	SListNode* newp = (SListNode*)malloc(sizeof(SListNode));
	newp->_data = x;
	SListNode* cur = plist->_head;
	for (; cur != pos && cur->_next != pos && cur; cur = cur->_next);
	if (cur == pos) {
		pos->_next = plist->_head;
		plist->_head = pos;
	}
	else if (cur->_next == pos) { 
		newp->_next = pos;
		cur->_next = newp;
	}
	else {
		printf("pos结点未在次链表中!\n");
	}
#else if //不遍历
	assert(pos && plist);
	SListNode* newp = (SListNode*)malloc(sizeof(SListNode));
	newp->_data = pos->_data;
	newp->_next = pos->_next;
	pos->_next = newp;
	pos->_data = x;
#endif
}
void SListEraseAfter(SListNode* pos) {//删除pos后面的结点
	assert(pos);
	SListNode* tmp = pos->_next;
	if (pos->_next != NULL) {
		pos->_next = pos->_next->_next;
	}
	free(tmp);
}
void SListRemove(SList* plist, SLTDataType x) {
#if 0
	assert(plist);
	SListNode* pos = SListFind(plist, x);
	SListNode* cur = plist->_head;
	for (; cur != pos && cur->_next != pos && cur; cur = cur->_next);
	if (cur == pos) {
		plist->_head = plist->_head->_next;
		free(pos);
	}
	else if (cur->_next == pos && pos) {
		cur->_next = pos->_next;
		free(pos);
	}
	else {
		printf("%d未在此链表中!\n", x);
	}
#else if //不遍历链表
	assert(plist);
	SListNode* pos = SListFind(plist, x);
	if (pos == NULL) {
		printf("链表中没有%d \n", x);
		return;
	}
	SListNode* tmp = pos->_next;
	pos->_data = pos->_next->_data;
	pos->_next = pos->_next->_next;
	free(tmp);
#endif
}
void SListPrint(SList* plist) {
	assert(plist);
	for (SListNode* cur = plist->_head; cur; cur = cur->_next) {
		printf("%d  ", cur->_data);
	}
	putchar('\n');
}

调试入口main.c

#include"alist.h"
int main() {
	SList test;
	SList* p = &test;
	SListInit(p);
	for (int i = 8; i > 0; --i) {
		SListPushFront(p, i);
	}
	for (int i = 9; i < 15; ++i) {
		SListPushBack(p, i);
	}
	SListPrint(p);
	SListPopFront(p);
	SListPopBack(p);
	SListPrint(p);
	printf("%d \n", SListFind(p, 6)->_data);
	SListInsertAfter(SListFind(p, 6), 77);
	SListInsertFront(p, SListFind(p, 10), 99);
	//SListInsertFront(p, SListFind(p, 20), 100);
	SListPrint(p);
	SListEraseAfter(SListFind(p, 12));
	//SListEraseAfter(SListFind(p, 20));
	SListPrint(p);
	SListRemove(p, 4);
	SListRemove(p, 20);
	SListPrint(p);
	SListDestory(p);
	system("pause");
	return 0;
}
  •  2、带头双向循环链表增删查改实现

    list.h声明

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;
typedef struct List
{
	ListNode* _head;
}List;
void ListInit(List* plist);
void ListDestory(List* plist);
void ListPushBack(List* plist, LTDataType x);
void ListPopBack(List* plist);
void ListPushFront(List* plist, LTDataType x);
void ListPopFront(List* plist);
ListNode* ListFind(List* plist, LTDataType x);
// 在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 删除pos位置的节点
void ListErase(ListNode* pos);
void ListRemove(List* plist, LTDataType x);
void ListPrint(List* plist);

list.c定义

#include"List.h"
void ListInit(List* plist) {//初始化
	plist->_head = (ListNode*)malloc(sizeof(ListNode));
	memset(&plist->_head->_data, 0, sizeof(LTDataType));
	plist->_head->_next = plist->_head;
	plist->_head->_prev = plist->_head;
}
void ListDestory(List* plist) {//销毁
	ListNode* tmp = plist->_head;
	for (;plist->_head->_next!=plist->_head;) {
		ListPopBack(plist);
	}
}
void ListPushBack(List* plist, LTDataType x) {//尾插
	ListNode* cur = (ListNode*)calloc(1, sizeof(ListNode));
	cur->_data = x;

	plist->_head->_prev->_next = cur;
	cur->_prev = plist->_head->_prev;
	cur->_next= plist->_head;
	plist->_head->_prev = cur;
}
void ListPopBack(List* plist) {//尾删
	ListNode* tmp = plist->_head->_prev;
	if (tmp != plist->_head) {
		tmp->_next->_prev = tmp->_prev;
		tmp->_prev->_next = tmp->_next;
		free(tmp);
	}
}
void ListPushFront(List* plist, LTDataType x) {//头插
	ListNode* cur = (ListNode*)calloc(1, sizeof(ListNode));
	cur->_data = x;

	plist->_head->_next->_prev = cur;
	cur->_next = plist->_head->_next;
	cur->_prev = plist->_head;
	plist->_head->_next = cur;
}
void ListPopFront(List* plist) {//头删, 对head后面的
	ListNode* tmp = plist->_head->_next;
	if(tmp!=plist->_head){
		tmp->_next->_prev = tmp->_prev;
		tmp->_prev->_next = tmp->_next;
		free(tmp);
	}
}
ListNode* ListFind(List* plist, LTDataType x) {
	for (ListNode* p = plist->_head->_next; p != plist->_head; p = p->_next) {
		if (p->_data == x) {
			return p;
		}
	}
	return NULL;
}

void ListInsert(ListNode* pos, LTDataType x) {// 在pos的前面进行插入
	assert(pos);
	ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
	cur->_data = x;
	pos->_prev->_next = cur;
	cur->_prev = pos->_prev;
	cur->_next = pos;
	pos->_prev = cur;
}
void ListErase(ListNode* pos) {// 删除pos位置的节点
	assert(pos);
	pos->_prev->_next = pos->_next;
	pos->_next->_prev = pos->_prev;
	free(pos);
}
void ListRemove(List* plist, LTDataType x) {
	ListErase(ListFind(plist, x));
}
void ListPrint(List* plist) {
	printf("head");
	for (ListNode* p = plist->_head->_next; p != plist->_head; p = p->_next) {
		printf("->%d", p->_data);
	}
	putchar('\n');
}

调试入口 main.c

#include"List.h"
int main() {//双向带头链表
	List test;
	ListInit(&test);
	for (int i = 10; i > 0; --i) {
		ListPushFront(&test, i);
	}
	ListPopBack(&test);
	ListPopFront(&test);
	ListInsert(ListFind(&test,5), 55);
	ListPrint(&test);
	ListRemove(&test, 55);
	ListPrint(&test);
	ListDestory(&test);
	ListPrint(&test);
	system("pause");
	return 0;
}

 


  • 4.顺序表和链表的区别与联系

顺序表:

优点:

空间连续、支持随机访问

缺点:

1.中间或前面部分的插入删除时间复杂度O(N)
2.增容的代价比较大。

链表 

优点:

1.任意位置插入删除时间复杂度为O(1)
2.没有增容问题,插入一个开辟一个空间。

缺点:

以节点为单位存储,不支持随机访问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值