顺序表简单功能的代码实现

今天起开始更新一期数据结构专栏,用来记录自己的数据结构知识相关的学习,第一章是最基础的顺序表。

文章目录

  • 前言
  • 一、初始化顺序表
  • 二 、销毁顺序表
  • 三、打印顺序表
  • 四、检查顺序表储存空间(不够则扩容)
  • 五、顺序表尾插
  • 六、顺序表尾删
  • 七、顺序表头插
  • 八、顺序表头删
  • 九、在顺序表中找到指定数据下标位置
  • 十、在顺序表中指定下标位置插入数据
  • 十一、在顺序表中指定下标位置删除数据
  • 十二、部分代码优化
  • 十三、头文件完整代码
  • 十四、源文件完整代码
  • 十五、总结


前言

(这里线性表等我直接搬百度解释了)

        线性表主要由顺序表示或链式表示。在实际应用中,常以栈、队列、字符串等特殊形式使用。

        顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存储结构或顺序映像(sequential mapping)。它以“物理位置相邻”来表示线性表中数据元素间的逻辑关系,可随机存取表中任一元素。

        链式表示指的是用一组任意的存储单元存储线性表中的数据元素,称为线性表的链式存储结构。它的存储单元可以是连续的,也可以是不连续的。在表示数据元素之间的逻辑关系时,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置),这两部分信息组成数据元素的存储映像,称为结点(node)。它包括两个域;存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称为指针或链。

(本篇内容参考B站比特科技学习)


typedef int SLDataType;

typedef struct SeqList {
	SLDataType* arr; //指向动态开辟的数组
	size_t size; //有效数据个数
	size_t capacity; //容量大小
}SL;

        首先把要操作的数据类型typedef一下,避免每次改数据时要一下子改整个代码中所有的地方。因为静态的顺序表缺陷比较大,每次只能提前开好数组,容易开大或者小从而导致空间的浪费,所以我们实现可以动态存储的顺序表。

初始化顺序表

void SeqListInit(SL* psl) {
    assert(psl);
    psl->arr = NULL;
    psl->size = 0;
    psl->capacity = 0;
}


        这里初始化赋值成NULL和0了,这个可以看个人习惯或者具体用途来自己实现初始化内容。由于传进来的是个指针,这里不加其实也没啥大问题但是我们尽量养成好习惯每次进来都assert判断一下是否为空指针,这样也可以检查一下有没有手误,下面的接口函数也都会加上assert。

销毁顺序表

void SeqListDestory(SL* psl) {
    assert(psl);
    free(psl->arr);
    psl->arr = NULL;
    psl->capacity = 0;
    psl->size = 0;
}

        因为顺序表是动态开辟的,用完之后要及时free掉并把相应的指针置为空避免造成内存泄漏的问题或者野指针的出现。

打印顺序表

void SeqListPrint(SL* psl) {
    assert(psl);
    for (int i = 0; i < psl->size; i++) {
        printf("%d ", psl->arr[i]);
    }
    printf("\n");
}

        因为顺序表是连续存储的不存在跳跃,并且有size存储着顺序表的数据个数,所以我们只需要for循环一个一个打印出来即可。

检查顺序表储存空间(不够则扩容)

void SeqListCheckCapacity(SL* psl) {
    assert(psl);
    if (psl->size == psl->capacity) {
        size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity*2;
        SLDataType* tmp = (SLDataType*)realloc(psl->arr,newcapacity * sizeof(SLDataType));
        if (tmp == NULL) {
            printf("realloc fail");
            exit(-1);//开辟失败退出
        }
        else {
            psl->arr = tmp;
            psl->capacity = newcapacity;
        }
    }
}

        在结构体中我们定义了一个size表示当前顺序表有效数据的个数,一个capacity表示能存储的个数的多少,而顺序表满了的情况就是size大于等于capacity了,所以在size等于capacity的时候我们进行扩容,这里用了一个三目操作符,是因为我们为了不浪费空间也能够满足需求都会对顺序表进行一个二倍的扩增,而如果顺序表中本来没有数据我们就给它四个数据个数空间的大小。这个时候为了避免开辟失败导致空指针的出现我们加了if语句,虽然这种情况一般不会发生,但是为了严谨养成好习惯我们还是加上这样一个判断。开辟成功我们就把指针指向这个新的数组,并且把增容后能存储的有效数据个数赋值给capacity。

顺序表尾插

void SeqListPushBack(SL* psl, SLDataType x){
    assert(psl);
    SeqListCheckCapacity(psl);

    psl->arr[psl->size] = x;
    ++psl->size;

       尾插的思路很简单,我们进行插入都要先检查一下空间够不够,然后因为顺序表的一个优点就是随机访问,我们尾插只需要知道最后一个数据,因为有size,所以arr[psl->size]就是最后一个数据后面的那个位置,直接把x赋值上去就行,最后把size++。

顺序表尾删

void SeqListPopBack(SL* psl) {
    assert(psl);
    assert(psl->size > 0);
    --psl->size;
}

        尾删的思路更简单,直接让size--即可,不过这里需要注意的是顺序表如果为空的话我们就不能再让size--了,所以这里我们用assert断言如果顺序表为空直接报错。

顺序表头插

void SeqListPushFront(SL* psl, SLDataType x) {
    assert(psl);
    SeqListCheckCapacity(psl);

    size_t end = psl->size;
    while (end) {
        psl->arr[end] = psl->arr[end - 1];
        --end;
    }
    psl->arr[0] = x;
    ++psl->size;
}

        头插比尾插稍微麻烦一点,但思路也很简单,我们需要将所有的数据往后移动一位,把这个新数据放到起始位置即可,这也是顺序表的一个缺点,增删改查时间复杂度最坏的情况是O(N)。具体实现就是我们定义一个临时变量end等于size,那么此时end对应下标的位置就是顺序表最后一个数据后面那个位置的下标,当它不等于0的时候,就把前一个数据赋值过来,这样每次操作完end向前挪动指向前一个,直到指向0也就是起始位置,此时再把x赋值过去就完成了头插,最后再把size++。

        这里还需要着重考虑的一点就是为什么我们不能从前往后赋值,是因为从头开始从前往后赋值的话每次前一个数会覆盖后一个数,从而导致出错,同样的道理下面头删时我们不能从末尾开始从后往前赋值。

顺序表头删

void SeqListPopFront(SL* psl) {
    assert(psl);
    assert(psl->size > 0);

    for (size_t begin = 0; begin < psl->size; begin++) {
        psl->arr[begin] = psl->arr[begin + 1];
    }
    --psl->size;
}

        头删的思路跟头插也有几分相似,同样跟尾删也有一个共同的问题就是顺序表为空的情况,这里首先assert一下,我们定义一个临时变量begin,从头开始每次将后面的一个数赋值给前一个数,直到begin到最后一个位置停止,这时只需要size--就行了。

在顺序表中找到指定数据下标位置

void SeqListFind(SL* psl, SLDataType x) {
    assert(psl);

    for (size_t i = 0; i < psl->size-1; i++) {
        if (x == psl->arr[i]) {
            printf("%zd ", i);
        }
    }
    printf("\n");
}

        找到指定下标的数据我们只需要从头遍历即可,找到后打印就行了,最坏情况的时间复杂度是O(N)。

在顺序表中指定下标位置插入数据

void SeqListInsert(SL* psl, size_t pos, SLDataType x) {
    assert(psl);
    assert(pos >= 0 && pos <= psl->size);
    SeqListCheckCapacity(psl);

    size_t end = psl->size;//防止在--中改变psl->size的值
    while(pos < end) {
        psl->arr[end] = psl->arr[end - 1];
        end--;
    }
    psl->arr[pos] = x;
    psl->size++;
}

        因为顺序表必须连续存储,所以我们需要先判断pos是否是合法位置,接着检查空间。因为插入就需要挪动那个位置后面的数据,我们不能改变size的值,所以提前拿一个end变量存起来,最开始的时候end指向的是顺序表最后一个数据后面的位置,如果end>pos,那么我们就把end前一个位置的值赋值给这个,再让end--指向前面一个数据的位置,最后end找到pos的位置跳出循环,把x的值赋给这里,再把size++,跟前面同样的道理我们不能从前往后赋值。

在顺序表中指定下标位置删除数据

void SeqListDelete(SL* psl, size_t pos) {
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);

	size_t begin = pos;
	while (begin < psl->size - 1) {
		psl->arr[begin] = psl->arr[begin + 1];
		begin++;
	}
	psl->size--;
}

        同样进来先判断,和前面差不多一样的道理,定义临时变量等于pos,每次把后面一个数据赋值到这个位置,然后begin++指向下一个数据位置,最后size--。

部分代码优化

void SeqListPushBack(SL* psl, SLDataType x) {
	SeqListInsert(psl, psl->size, x);
}
void SeqListPopBack(SL* psl) {
	SeqListDelete(psl, psl->size - 1);
}
void SeqListPushFront(SL* psl, SLDataType x) {
	SeqListInsert(psl, 0, x);
}
void SeqListPopFront(SL* psl) {
	SeqListDelete(psl, 0);
}

        我们实现了任意位置的插入和删除,自然尾插尾删头插头删也是顺序表中的位置,尾删就是再size下标位置插入,尾删就是把size-1下标位置删除,下面一样的道理。

头文件完整代码

#pragma once

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

typedef int SLDataType;

typedef struct SeqList {
    SLDataType* arr; //指向动态开辟的数组
    size_t size; //有效数据个数
    size_t capacity; //容量大小
}SL;

//初始化顺序表
void SeqListInit(SL* psl);

//销毁顺序表
void SeqListDestory(SL* psl);

//打印顺序表
void SeqListPrint(SL* psl);

//检查顺序表储存空间(不够则扩容)
void SeqListCheckCapacity(SL* psl);

//顺序表尾插
void SeqListPushBack(SL* psl, SLDataType x);

//顺序表尾删
void SeqListPopBack(SL* psl);

//顺序表头插
void SeqListPushFront(SL* psl, SLDataType x);

//顺序表头删
void SeqListPopFront(SL* psl);

//在顺序表中找到指定数据下标位置
void SeqListFind(SL* psl, SLDataType x);

//在顺序表中指定下标位置插入数据
void SeqListInsert(SL* psl, size_t pos, SLDataType x);

//在顺序表中指定下标位置删除数据
void SeqListDelete(SL* psl, size_t pos);

源文件完整代码

#include "SeqList.h"

void SeqListInit(SL* psl) {
	assert(psl);
	psl->arr = NULL;
	psl->size = 0;
	psl->capacity = 0;
}

void SeqListDestory(SL* psl) {
	assert(psl);
	free(psl->arr);
	psl->arr = NULL;
	psl->capacity = 0;
	psl->size = 0;
}

void SeqListPrint(SL* psl) {
	assert(psl);
	for (int i = 0; i < psl->size; i++) {
		printf("%d ", psl->arr[i]);
	}
	printf("\n");
}

void SeqListCheckCapacity(SL* psl) {
	assert(psl);
	if (psl->size == psl->capacity) {
		size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity*2;
		SLDataType* tmp = (SLDataType*)realloc(psl->arr,newcapacity * sizeof(SLDataType));
		if (tmp == NULL) {
			printf("realloc fail");
			exit(-1);//开辟失败退出
		}
		else {
			psl->arr = tmp;
			psl->capacity = newcapacity;
		}
	}
}

//void SeqListPushBack(SL* psl, SLDataType x){
//	assert(psl);
//	SeqListCheckCapacity(psl);
//
//	psl->arr[psl->size] = x;
//	++psl->size;
//}

void SeqListPushBack(SL* psl, SLDataType x) {
	SeqListInsert(psl, psl->size, x);
}

//void SeqListPopBack(SL* psl) {
//	assert(psl);
//	assert(psl->size > 0);
//	--psl->size;
//}

void SeqListPopBack(SL* psl) {
	SeqListDelete(psl, psl->size - 1);
}



//void SeqListPushFront(SL* psl, SLDataType x) {
//	assert(psl);
//	SeqListCheckCapacity(psl);
//
//	size_t end = psl->size;
//	while (end) {
//		psl->arr[end] = psl->arr[end - 1];
//		--end;
//	}
//	psl->arr[0] = x;
//	++psl->size;
//}

void SeqListPushFront(SL* psl, SLDataType x) {
	SeqListInsert(psl, 0, x);
}

//void SeqListPopFront(SL* psl) {
//	assert(psl);
//	assert(psl->size > 0);
//
//	for (size_t begin = 0; begin < psl->size; begin++) {
//		psl->arr[begin] = psl->arr[begin + 1];
//	}
//	--psl->size;
//}

void SeqListPopFront(SL* psl) {
	SeqListDelete(psl, 0);
}

void SeqListFind(SL* psl, SLDataType x) {
	assert(psl);

	for (size_t i = 0; i < psl->size-1; i++) {
		if (x == psl->arr[i]) {
			printf("%zd ", i);
		}
	}
	printf("\n");
}

void SeqListInsert(SL* psl, size_t pos, SLDataType x) {
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);
	SeqListCheckCapacity(psl);

	size_t end = psl->size;//防止在--中改变psl->size的值
	while(pos < end) {
		psl->arr[end] = psl->arr[end - 1];
		end--;
	}
	psl->arr[pos] = x;
	psl->size++;
}

void SeqListDelete(SL* psl, size_t pos) {
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);

	size_t begin = pos;
	while (begin < psl->size - 1) {
		psl->arr[begin] = psl->arr[begin + 1];
		begin++;
	}
	psl->size--;
}

总结

        数据结构的学习需要多动手画画图敲敲代码,这样才能进步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值