数据结构——顺序表

本文介绍了顺序表的物理顺序结构,包括静态和动态存储方式。静态存储使用固定大小的数组,而动态存储允许自动扩容。对于插入和删除操作,动态存储的顺序表在最坏和平均情况下时间复杂度为O(n)。文章还详细阐述了插入和删除操作的实现细节,并提供了C++代码示例。
摘要由CSDN通过智能技术生成

1.介绍

物理顺序结构采用一段物理地址连续的存储单元依次存储数据元素的线性结构,来表示逻辑的线性结构。语言上可以采用数组存储。在数组上完成数据的增删查改。

物理结构形似下图:

顺序表是随机存储,即访问任一元素都是消耗的时间都相同。

假设表首元素地址为pFirst,每个元素大小为d,则我们想要读写一个元素,假设位序为i,则利用address=pFirst+(i-1)d,即可转到所需要的元素地址。

C++数组下标从0开始,因此公式简化为address=pFirst+id。

2.存储方式

我们设置的类名为SqList,意思为sequence list,即顺序表。

(1)静态存储

采用语言内置的数组表示,因此元素个数一旦确定,不可更改。

因此,受到一定限制,不可动态增长。

template<typename T>
class SqLsit
{
    static const int maxSize = 100;
    T data[maxSize];
    int length;
};

(2)动态存储

可以自动扩容,以适应程序中大小不同的需求。

操作:

(1)插入

每当顺序表插入一个位置时,我们首先确保这个元素和其他元素紧密排在一起,也就是说插入之后仍然要保持一个顺序表的结构。

因此,下面的插入的元素2的情况就不合格。

 用if语句确保插入的下标在[0,length]范围内。

每当顺序表插入时,首先需要确保我们申请的内存足以再存一个元素。如果不够,就需要先扩容

我们需要将插入下标内的元素和其之后的元素往后挪一位。如下图。

挪动的元素个数决定了时间复杂度。

如果,插入在表尾,则不需要挪动元素。最佳时间复杂度O(1)。

如果,插入在表头,则需要挪动全部n个元素。最坏时间复杂度O(n)。

平均情况下,插入的情况有n+1种(注意别忘了位序为数组长度的位置也可以插入),分别是挪动0个元素到挪动n个元素的情况、则平均为

(1+2+3+...+n)/(n+1)=n/2,则平均时间复杂度为O(n)。

(2)删除

如果,删除表尾,则不需要挪动元素。最佳时间复杂度O(1)。

如果,删除表头,则需要挪动全部n-1个元素。最坏时间复杂度O(n)。

平均情况下,挪动的情况有n种,分别是挪动0个元素到挪动n-1个元素的情况、则平均为

(1+2+3+...+n-1)/n=(n-1)/2,则平均时间复杂度为O(n)。

其他操作较为简单,涉及代码如下:

#pragma once
#include<iostream>
template<typename T>
class SqList
{
	T* data;
	unsigned maxSize;
	unsigned length;
	void resize();//每次扩容两倍
public:
	SqList(unsigned m=10);//创建
	~SqList();//销毁
	void insert(unsigned i,const T& val);//将val插入到数组下标为i的地方
	void remove(unsigned i);//删除数组下标i元素
	void change(unsigned i, const T& val);//修改数组下标i元素为val
	int search(const T& val) const;//查找第一次与之相同的元素,返回数组下标
	void print()const;//输出数组
	bool empty()const;//判断是否是个空表
	unsigned size()const;//返回表长
};
//每次resize()时间复杂度都为O(n)
template<typename T>
void SqList<T>::resize()
{
    //扩容两倍的原因,是防止一旦动态数组满了,就会使插入元素变成O(n)级,
    //这样,每次插入就不需要              频繁的调动resize
    //扩容多倍又很容易浪费内存,因此扩容两倍是个好的选择。
	T* p = data;
	data = new T[maxSize * 2];
	for (unsigned i = 0; i < length; i++)
		data[i] = p[i];
	maxSize *= 2;
	delete[]p;
}
//空间复杂度O(n)
template<typename T>
SqList<T>::SqList(unsigned m)
{
	maxSize = m;
	data = new T[maxSize];
	length = 0;
}
//时间复杂度O(1)
template<typename T>
SqList<T>::~SqList()
{
	delete[] data;
}
//insert时间复杂度O(n)
template<typename T>
void SqList<T>::insert(unsigned i,const T& val)
{
	//等于小于length都没问题,因为i是非负类型,不用考虑小于0的情况
	if (i > length)
		throw("out of range!");
	//自动扩容
	if (length == maxSize)
		resize();
	//先将i和i之后的元素后移一位
	for (unsigned u = length; u > i; u--)
	{
		data[u] = data[u - 1];
	}
	//插入
	data[i] = val;
	length++;
}
//remove时间复杂度O(n)
template<typename T>
void SqList<T>::remove(unsigned i)
{
	if (i >= length)
		throw("out of range!");
	//将i之后的元素前移一位
	for (unsigned u = i; u < length-1; u++)
		data[u] = data[u + 1];
	length--;
}
//change时间复杂度O(1),也可以重载下标运算符[]
template<typename T>
void SqList<T>::change(unsigned i, const T& val)
{
	if (i >= length)
		throw("out if range!");
	data[i] = val;
}
//search时间复杂度O(n)
template<typename T>
int SqList<T>::search(const T& val) const
{
	for (int i = 0; i < length; i++)
	{
		if (data[i] == val)
			return i;
	}
	//返回-1表示没找的到
	return -1;
}
//print时间复杂度O(n)
template<typename T>
void SqList<T>::print()const
{
	for (unsigned i = 0; i < length; i++)
	{
		std::cout << data[i] << " ";
	}
	std::cout << std::endl;
}
//empty时间复杂度O(1)
template<typename T>
bool SqList<T>::empty()const
{
	if (!length)
		return true;
	else return false;
}
//size时间复杂度O(1)
template<typename T>
unsigned SqList<T>::size()const
{
	return length;
}

由于题目要求输出逆置时间,所以我们需要计算逆置操作的时间。可以使用Python的time模块中的time()函数,记录每次逆置操作前后的时间,计算时间差即为逆置时间。 顺序表逆置操作可以使用Python中的切片操作,时间复杂度为O(n),其中n为表长。代码如下: ```python import time # 构建顺序表 n = 10000000 a = list(range(1, n+1)) # 逆置操作 start = time.time() a = a[::-1] end = time.time() # 输出逆置时间 print("顺序表逆置时间:", end-start) ``` 单链表逆置操作可以使用三个指针prev、cur、nxt,分别指向前一个节点、当前节点、下一个节点,依次遍历链表,将每个节点的next指针指向前一个节点即可。时间复杂度同样为O(n),其中n为链表长度。代码如下: ```python import time # 定义链表节点类 class ListNode: def __init__(self, val=0, nxt=None): self.val = val self.next = nxt # 构建单链表 n = 10000000 dummy = ListNode(0) prev = dummy for i in range(1, n+1): cur = ListNode(i) prev.next = cur prev = cur head = dummy.next # 逆置操作 start = time.time() prev = None cur = head while cur: nxt = cur.next cur.next = prev prev = cur cur = nxt end = time.time() dummy.next = prev # 输出逆置时间 print("单链表逆置时间:", end-start) ``` 这里有一个小技巧,可以使用头结点dummy来避免单链表的边界情况(即逆置操作需要特判表头节点)。具体方法是在构建链表时,先建立一个值为0的dummy节点,将其next指向第一个节点,最后再将dummy.next赋给head即可。逆置操作结束后,再将dummy.next赋给prev,得到逆置后的新链表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值