顺序表:
顺序表(Sequential List)是一种使用顺序存储结构实现的线性表,它将线性表的元素存储在一段连续的存储单元中。顺序表的特点是元素在内存中的存储位置是连续的,通过元素在存储单元中的相对位置来表示元素之间的逻辑顺序。
顺序表通常由以下几个要素组成:
1. 数据存储区:顺序表中用来存储元素的一段连续存储空间,通常使用数组来实现。
2. 表长度:记录当前顺序表中元素的个数。
3. 最大容量:记录顺序表所分配的存储空间的最大容量,防止数组越界。
typedef struct Sqlist
{
Sqlist* _data;
int _size;
int _capacity;
}Sqlist;
构造函数与析构函数
Sqlist::Sqlist(int capacity = 4)
{
_capacity = capacity;
Datatype* temp = (Datatype*)malloc(sizeof(Datatype) * _capacity);
if (temp == nullptr)
{
perror("malloc");
exit(-1);
}
_data = temp;
_size = 0;
}
Sqlist::~Sqlist()
{
free(_data);
_data = nullptr;
_capacity = _size = 0;
}
可以理解为初始化函数与内存释放
插入与扩容
void Sqlist::Newcapacity() {
_capacity *= 2;
Datatype* newData = (Datatype*)realloc(_data, sizeof(Datatype) * _capacity);
if (newData == nullptr) {
perror("realloc");
exit(-1);
}
else {
_data = newData;
}
}
void Sqlist::Insert(Datatype data)
{
if (_size == _capacity)//判断当前线性表是否已满
{
Newcapacity();//更新线性表大小
}
_data[_size++] = data;
}
后置++ :先插入 数据再更新_size的大小
查询
遍历对象数组
int Sqlist::seek(Datatype data)
{
for (int i = 0; i < _size; i++)
{
if (_data[i] == data)
{
return i;
}
}
cout << "false";
return -1;
}
返回值是数组索引,我们定义如果返回-1就是没有找到
删除更改
void Sqlist::Erase(int index) {
assert(index >= 0 && index < _size);
// 将指定位置后面的元素向前移动一个位置覆盖被删除的元素
for (int i = index; i < _size - 1; ++i) {
_data[i] = _data[i + 1];
}
--_size;
}
void Sqlist::Alter(int index, Datatype newData) {
assert(index >= 0 && index < _size); // 确保索引合法
_data[index] = newData; // 将指定位置的元素替换为新的数据
}
我这里将查询与删除更改配合起来使用,直接调用查询的索引。对于头插头删的代码,就是在数组中删除,数据,这正是顺序表的弊端,需要不断移动数组中的元素。 9
顺序表的优点包括:
1. 随机访问:由于元素在内存中是连续存储的,因此可以通过下标直接访问任意位置的元素,实现了常数时间的随机访问。
2. 存储密度高:不需要额外的指针存储元素之间的关系,因此存储密度高,节省了存储空间。
顺序表的缺点包括:
1. 插入和删除操作效率低:在顺序表中进行插入和删除操作需要移动大量元素,时间复杂度为O(n),效率较低。
2. 空间浪费:顺序表需要预先分配一定大小的存储空间,可能会导致存储空间的浪费。
3. 容量固定:顺序表的容量在创建时确定,不能动态扩容,当存储空间不足时需要重新分配空间,可能导致数据复制和性能损耗。
顺序表常见的应用场景包括需要频繁进行随机访问,且元素个数相对稳定的情况下,如静态查找表、稠密矩阵等。在编程中,顺序表的实现比较简单,常被用来实现数组和列表等数据结构。
链表:
链表(Linked List)是一种常见的数据结构,它由一系列节点(Node)组成,每个节点包含两部分:数据域(Data)和指针域(Pointer)。数据域用来存储数据,指针域用来指向下一个节点,这样将所有节点按顺序连接起来就形成了链表。链表中的第一个节点称为头节点,最后一个节点的指针域通常指向空(nullptr 或 NULL),表示链表的结束。
链表可以分为单向链表、双向链表和循环链表等不同类型,根据指针域的不同连接方式。常见的有单向链表和双向链表:
1. 单向链表(Singly Linked List):每个节点只有一个指针域,指向其后继节点。
2. 双向链表(Doubly Linked List):每个节点有两个指针域,一个指向其前驱节点,一个指向其后继节点。我这里以单链表为例子
// 定义链表节点结构体
struct ListNode {
DateType _data;
ListNode* _next;
};
由于C++构造函数会直接将头节点给赋值甚至我们不处理还是随机值,所以,我这里使用一个结构体代表链表本体,用List类储存他的头跟尾(插入的时候减少循环)
ListNode* _head;
ListNode* _tail;
构造函数与析构函数
List::List()
{
_tail = _head = nullptr;
}
List::~List()
{
ListNode* cur = _head;
while (cur != nullptr)
{
ListNode* temp = cur->_next;
free(cur);
cur = temp;
}
_head = nullptr;
_tail = nullptr;
}
节点创建
ListNode* List::create(DateType data)
{
ListNode* temp = (ListNode*)malloc(sizeof(List));
if (temp == nullptr)
{
perror("malloc");
exit(-1);
}
temp->_data = data;
temp->_next = nullptr;
return temp;
}
插入节点时,我们需要节点分配空间,直接封装成函数,省事
头,尾插入
void List::Pushback(DateType data)
{
if (_head == nullptr)
{
_head = create(data);
_tail = _head;
return;
}
ListNode* cur = create(data);
_tail->_next = cur;
_tail = cur;
}
void List::Pushfront(DateType data)
{
ListNode* temp = create(data);
if (_head == nullptr)
{
_head = temp;
_tail = _head;
return;
}
temp = _head->_next;
_head = temp;
if (_head = _tail)
{
_tail = temp->_next;
}
}
包括头插,尾插,这时候我们保存尾节点就起到作用了,这里也需要注意头插的特殊情况
查找
ListNode* List::Seek(int target) {
ListNode* current = _head; // 从链表头节点开始遍历
while (current != nullptr) {
if (current->_data == target) {
return current; // 找到节点,返回节点指针
}
current = current->_next; // 移动到下一个节点
}
return nullptr; // 没有找到节点,返回空指针
}
插入
void List::Insert(ListNode* node, DateType data)
{
ListNode* cur = _head;
while (cur != node)
{
cur = cur->_next;
}
ListNode* temp = create(data);
temp->_next = cur->_next;
cur->_next = temp;
}
删除
void List::Remove(ListNode* node)
{
ListNode* cur = _head;
ListNode* tail = cur;
while (cur != node)
{
tail = cur;
cur = cur->_next;
}
tail->_next = cur->_next;
free(cur);
cur = nullptr;
}
注意要先进行插入节点与后续节点的链接
链表的优点包括:
- 插入和删除操作高效:在链表中插入或删除一个节点的时间复杂度为 O(1),只需改变节点的指针,不需要移动其他节点。
- 空间利用灵活:链表的节点在内存中分布不连续,可以根据实际需求动态地分配和释放内存。
链表的缺点包括:
- 随机访问效率低:由于链表中的元素不是连续存储的,因此不能像数组那样通过下标直接访问元素,需要从头节点开始遍历,时间复杂度为 O(n)。
- 需要额外的指针空间:链表中的每个节点都需要额外的指针来存储后继节点的地址,占用了额外的空间。
链表常被用于实现其他数据结构,如栈、队列和哈希表等,也常用于解决与数据结构相关的问题,如反转链表、合并链表等。