【数据结构与算法篇】动态顺序表的C++实现

1. 顺序表简介

  • 线性表的顺序存储是指在内存中用地址连续的一块存储空间 按照顺序存放线性表的各元素, 用这种存储形式进行存储的线性表被称为顺序表
  • 顺序表是一种自定义的数据类型
  • 往往通过结构体来实现
  • 顺序表分为静态和动态两种

2. 动态顺序表的封装

利用C++的结构体来实现


typedef int SLDataType;     // 便于顺序存储数据类型的更换, 示例为int
typedef struct SeqList
{
	SLDataType* a;       // 指针变量, 指向一个数组
	int size;            // 顺序表所存储元素的个数
	int capacity;        // 顺序表的容量
}Seq;       // 起别名

3. 顺序表的初始化


void SLInit(Seq* s)
{
	s->a = new SLDataType[sizeof(SLDataType) * 4];   // 定义一个数组, 使用new在堆区申请指定内存 , 申请了四个元素大小的内存
	if (s->a == NULL)
	{
		perror("new failed");     // 检查, 初始化失败,打印标志
		exit(-1);                 // 强制退出整个程序
		;
	}
	s->capacity = 4;               // 设置容量
	s->size = 0;                   // 当前存储元素个数为0
}

4. 顺序表的增删改查

1> 向顺序表中插入元素

1) 头插法

void SLPushFront(Seq* s, SLDataType val)
{
	if (s->size == s->capacity)   // 判断顺序表是否已满
	{
		SLOpenCapacity(s);       // 满了就扩容 自定义的顺序表扩容函数
		for (int end = s->size - 1; end >= 0; end--)
		{        // 插入元素部分
			s->a[end + 1] = s->a[end];
		}
		s->a[0] = val;
		s->size++;
	}
	else
	{
		for (int end = s->size - 1; end >= 0; end--)
		{      // 插入元素部分
			s->a[end + 1] = s->a[end];
		}
		s->a[0] = val;
		s->size++;
	}
}

2) 尾插法

void SLPushBack(Seq* s, SLDataType val)
{
	if (s->size == s->capacity)    // 判断顺序表是否已满
	{
		SLOpenCapacity(s);        // 满了就扩容 自定义的顺序表扩容函数
		s->a[s->size] = val;     // 插入元素部分
		s->size++;
	}
	else
	{
		s->a[s->size] = val;     // 插入元素部分
		s->size++;
	}
}

3) 指定位置插入
void SLInsert(Seq* s, int pos, SLDataType val)
{
	if (pos <= s->size&& pos >= 0)    // 判断指定索引是否合法
	{
		if (s->size == s->capacity)    // 判断顺序表是否已满
		{
			SLOpenCapacity(s);      // 满了扩容
		}
		for (int i = s->size; i > pos; i--)    
		{       // 插入元素部分
			s->a[i] = s->a[i - 1];
		}
		s->a[pos] = val;
		s->size++;
	}
	else
	{
		cout << "请输入有效索引" << endl;
	}
}

2> 删除顺序表中的元素

1) 头删法

void SLPopFront(Seq* s)
{
	if (s->size > 0)       // 判断顺序表是否为空
	{
		for (int begin = 1; begin < s->size; begin++)
		{        // 删除元素部分
			s->a[begin - 1] = s->a[begin];
		}
		s->size--;
	}
	else
	{
		assert(s->size > 0);    // 使用断言函数提示程序运行错误
	}
}

2) 尾删法

void SLPopBack(Seq* s)
{
	if (s->size > 0)    // 判断顺序表是否为空
	{
		s->size--;        // 顺序表中元素的删除并非真的删除, 而是索引的变法, 使得体现在遍历顺序表中元素时,可视为已被删除
	}
}

3) 删除指定位置元素

void SLErase(Seq* s, int pos)
{
	if (pos >= 0 && pos < s->size)    // 判断指定索引是否非法
	{
		for (int i = pos; i < s->size; i++)
		{      // 删除元素部分
			s->a[pos] = s->a[pos + 1];
		}
		s->size--;
	}
}

4) 删除值为指定值的所有元素

void SLErase_P(Seq* s, SLDataType val)
{
	if (s->size > 0)
	{
		for (int i = 0; i < s->size; i++)
		{
			if (s->a[i] == val)
			{
				for (; i < s->size; i++)
				{
					s->a[i] = s->a[i + 1];
				}
				s->size--;
			}
		}
	}
}

3> 遍历顺序表


void SLPrint(Seq* s)
{
	for (int i = 0; i < s->size; i++)
	{
		cout << s->a[i] << " ";
	}
	cout << endl;
}

4> 在顺序表中查找值为val的元素,

存在返回其索引, 不存在 返回 -1


int SLFindElement(Seq* s, SLDataType val)
{
	for (int i = 0; i < s->size; i++)
	{
		if (s->a[i] == val)
			return i;
	}

	return s->size;
}

5> 修改顺序表中指定位置的元素


void SLModify(Seq* s, int pos, SLDataType val)
{
	if (pos >= 0 && pos < s->size)
	{
		s->a[pos] = val;
	}
}

5. 顺序表容量的扩充


void SLOpenCapacity(Seq* s)
{
	SLDataType* temp = new SLDataType[s->capacity * 2];
	for (int i = 0; i < s->size; i++)
	{
		temp[i] = s->a[i];
	}

	delete[] s->a;
	s->a = temp;
	s->capacity *= 2;
}

6. 顺序表的销毁


void SLDestory(Seq* s)
{
	if (s->a != NULL)
	{
		delete[] s->a;
		s->a = NULL;
		s->capacity = 0;
		s->size = 0;
	}
}

文中代码多次调用 自定义函数 SLDestory(Seq* s) ,用于顺序表容量不足时, 进行扩容。

7. 顺序表的优缺点

  • 优点 :
    • 元素支持随机访问, 排序后支持二分查找
    • CPU高速缓存命中率比较高
    • (也就是 物理上存储空间连续, 缓存利用率高)
  • 缺点 :
    • 头部或中间插入删除数据需要挪动数据, 效率低
    • 空间不够,只能扩容, 扩容有消耗
      - 原地扩容
      - 异地扩容 (需要拷贝原数组)
    • 倍数扩容, 空间用不完, 存在空间的浪费
  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值