一、概述
1.1 数据结构必知
- 数据结构的逻辑结构分为集合结构、线性结构、树形结构、图形结构。
- 数据结构的存储结构分为顺序结构、链式结构、索引结构、散列结构。
- 常见的八大数据结构有数组(Array)、栈(Stack)、队列(Queue)、链表(Linked List)、树(Tree)、图(Graph)、散列表(Hash)。
1.2 关注点
- 如何方便的存储(增删改查、遍历、排序)数据:有序集合(List)、无序集合(Set)。
- 如何约束数据的线性关系:栈(Stack,先进后出)、队列(先进先出)。
- 如何表示多个数据之间的关系:树(Tree,一对多)、图(Graph,多对多)。
- 如何存储键值类型的数据对:映射(Map)。
- 如何提高性能:集合容器本身需要根据数据量的多少而选择合适的数据结构。
二、简单线性表
2.1 数组和链表的关系
编程语言诞生之初,早已经有了指针和内存······
- 数组:指针指向了一块若干数据大小的连续内存,可以通过指针或下标去访问。
- 链表: 指针指向了一块1个数据大小的内存并且这个内存中保存着指向另一块1个数据大小的内存的指针,只能通过指针去访问。
2.2 头节点的作用
设置头节点只是为了操作的方便。如果不设置头节点,从图中例子的对比可以看到:处理第一个元素是比较麻烦的。
如果采用指针传参(地址值的值传递),指针类型的实参不可能在函数内部指向另一个地址。
如果采用引用传参(引用的本质是常指针),引用只是变量的别名,在申明时就已经确定了指向并且永远不能更改其指向。
int a = 5;
int& ref = a; // 等价于 int* const p = &a;
通常的解决方案有:
-
将在函数内改变指向的指针作为返回值返回,在调用时更新指针。(如图中右侧示例,并且这也仅仅是为了处理第一个元素的问题而不得不这么做)
-
采用二级指针传参,将指针的地址值传入函数即可以在函数内改变指针的指向。
Node** headContainer = &head;
-
将头指针设为全局变量。
-
将头指针设为对象的属性
2.3 线性表结构约定
2.4 有序集合接口——list.h
#ifndef DEMO_TEST_LIST_H
#define DEMO_TEST_LIST_H
#include <string>
#include <iostream>
template<typename E>
class List {
public:
virtual ~List();
virtual int size() = 0;
virtual bool isEmpty() = 0;
/**
* 向表中添加元素
* @param element
*/
virtual void add(E element) = 0;
/**
* 向表中指定位置插入元素
* @param index
* @param element
*/
virtual void add(int index, E element) = 0;
/**
* @param index
* @return 表中指定位置的元素
*/
virtual E get(int index) = 0;
/**
* @param element
* @return 获取某元素在表中的下标
*/
virtual int indexOf(E element) = 0;
/**
* 更改表中指定位置的元素
* @param index
* @param element
*/
virtual void set(int index, E element) = 0;
/**
* 移除表中某个元素
* @param element
*/
virtual void removeByElement(E element) = 0;
/**
* 移除表中指定下标的元素
* @param index
*/
virtual void removeByIndex(int index) = 0;
/**
* 移除表中全部元素
*/
virtual void removeAll() = 0;
virtual std::string toString() = 0;
};
template<typename T>
List<T>::~List() {
std::cout << "默认析构函数";
}
#endif //DEMO_TEST_LIST_H
2.5 队列接口——queue.h
#ifndef DEMO_TEST_QUEUE_H
#define DEMO_TEST_QUEUE_H
template<typename E>
class Queue {
public:
/**
* 将指定的元素插入此队列
* @return 是否插入成功
*/
virtual bool offer(E element) = 0;
/**
* 获取队列的头但不移除
* @return 队列的头
*/
virtual E peek() = 0;
/**
* 获取队列的头并移除
* @return 队列的头
*/
virtual E poll() = 0;
};
#endif //DEMO_TEST_QUEUE_H
2.6 栈接口——stack.h
#ifndef DEMO_TEST_STACK_H
#define DEMO_TEST_STACK_H
template<typename E>
class Stack {
public:
/**
* 压栈
* @param element
*/
virtual void push(E element) = 0;
/**
* 获取栈顶元素但不出栈
* @return 栈顶元素
*/
virtual E peek() = 0;
/**
* 获取栈顶元素并出栈
* @return 栈顶元素
*/
virtual E pop() = 0;
};
#endif //DEMO_TEST_STACK_H
三、双向链表实现线性表
3.1 双向链表图示
注:和图中不完全相符
- 代码中的链表用
first
和last
表示首尾元素的指针(也即head
指针和tail
指针)- 节点用
prev
和next
表示前驱和后继指针。
3.2 节点代码——node.h
#ifndef DEMO_TEST_NODE_H
#define DEMO_TEST_NODE_H
template<typename E>
struct Node {
E data;
Node<E>* prev;
Node<E>* next;
};
#endif //DEMO_TEST_NODE_H
3.3 双向链表代码——linked_list.h
#ifndef DEMO_TEST_LINKED_LIST_H
#define DEMO_TEST_LINKED_LIST_H
#include "node.h"
#include "list.h"
#include "stack.h"
#include "queue.h"
// 实现toString()需要用到
using namespace std;
template<typename E>
class LinkedList : public List<E>, public Stack<E>, public Queue<E> {
private:
int length;
Node<E>* first;
Node<E>* last;
public:
LinkedList() : length(0), first(nullptr), last(nullptr) {}
int size() override {
return this->length;
}
bool isEmpty() override {
return this->length == 0;
}
void add(E element) override {
// 新建节点
Node<E>* node = new Node<E>();
node->data = element;
node->next = nullptr;
if (this->first == nullptr) {
// 空链表
node->prev = nullptr;
this->first = node;
this->last = node;
} else {
// 非空链表
node->prev = this->last;
this->last->next = node;
this->last = node;
}
this->length++;
}
void add(int index, E element) override {
if (index > this->length - 1) {
// 下标越界
throw "Index Out Of Bounds";
}
// 新建节点
Node<E>* node = new Node<E>();
node->data = element;
if (index == 0) {
// 插入到第一个位置
node->prev = nullptr;
node->next = this->first;
this->first->prev = node;
this->first = node;
} else if (index == this->length - 1) {
// 插入到最后一个位置
node->next = nullptr;
node->prev = this->last;
this->last->next = node;
this->last = node;
} else {
// 寻找插入位置
Node<E>* toInsert = nullptr;
if (index <= this->length / 2) {
Node<E>* p = this->first->next;
int i = 1;
while (i != index) {
i++;
p = p->next;
}
toInsert = p;
} else if (index > this->length / 2) {
Node<E>* p = this->last->prev;
int i = this->length - 2;
while (i != index) {
i--;
p = p->prev;
}
toInsert = p;
}
// 此处toInsert必定不为空指针
Node<E>* before = toInsert->prev;
before->next = node;
node->prev = before;
node->next = toInsert;
toInsert->prev = node;
}
this->length++;
}
E get(int index) override {
if (index > this->length - 1) {
// 下标越界
throw "Index Out Of Bounds";
}
Node<E>* toGet = nullptr;
if (index <= this->length / 2) {
// 从前向后查询
Node<E>* p = this->first;
int i = 0;
while (i != index) {
i++;
p = p->next;
}
toGet = p;
} else if (index > this->length / 2) {
// 从后向前查询
Node<E>* p = this->last;
int i = this->length - 1;
while (i != index) {
i--;
p = p->prev;
}
toGet = p;
}
return toGet->data;
}
int indexOf(E element) override {
Node<E>* p = this->last;
int i = this->length - 1;
while (p != nullptr) {
if (p->data == element) {
break;
}
i--;
p = p->prev;
}
return i;
}
void set(int index, E element) override {
Node<E>* toSet = nullptr;
if (index > this->length - 1) {
// 下标越界
throw "Index Out Of Bounds";
} else if (index <= this->length / 2) {
// 从前向后查询
Node<E>* p = this->first;
int i = 0;
while (i != index) {
i++;
p = p->next;
}
toSet = p;
} else if (index > this->length / 2) {
// 从后向前查询
Node<E>* p = this->last;
int i = this->length - 1;
while (i != index) {
i--;
p = p->prev;
}
toSet = p;
}
// 此处toSet必定不会为空指针
toSet->data = element;
}
void removeByElement(E element) override {
Node<E>* toRemove = nullptr;
// 查询待删除的节点
Node<E>* p = this->first;
while (p != nullptr) {
if (p->data == element) {
toRemove = p;
break;
}
p = p->next;
}
if (toRemove != nullptr) {
// 如果查询到先连接待删除节点的前驱和后继,然后释放待删除节点的内存
if (toRemove == this->first) {
// 待删除的是第一个元素
Node<E>* after = toRemove->next;
after->prev = nullptr;
this->first = after;
} else if (toRemove == this->last) {
// 待删除的是最后一个元素
Node<E>* before = toRemove->prev;
before->next = nullptr;
this->last = before;
} else {
// 待删除的是中介的元素
Node<E>* before = toRemove->prev;
Node<E>* after = toRemove->next;
before->next = after;
after->prev = before;
}
delete toRemove;
this->length--;
}
}
void removeByIndex(int index) override {
Node<E>* toRemove = nullptr;
//查询待删除的节点
if (index > this->length - 1) {
// 下标越界
throw "Index Out Of Bounds";
} else if (index <= this->length / 2) {
// 从前向后查询
Node<E>* p = this->first;
int i = 0;
while (i != index) {
i++;
p = p->next;
}
toRemove = p;
} else if (index > this->length / 2) {
// 从后向前查询
Node<E>* p = this->last;
int i = this->length - 1;
while (i != index) {
i--;
p = p->prev;
}
toRemove = p;
}
if (toRemove != nullptr) {
// 如果查询到先连接待删除节点的前驱和后继,然后释放待删除节点的内存
if (toRemove == this->first) {
// 待删除的是第一个元素
Node<E>* after = toRemove->next;
after->prev = nullptr;
this->first = after;
} else if (toRemove == this->last) {
// 待删除的是最后一个元素
Node<E>* before = toRemove->prev;
before->next = nullptr;
this->last = before;
} else {
// 待删除的是中介的元素
Node<E>* before = toRemove->prev;
Node<E>* after = toRemove->next;
before->next = after;
after->prev = before;
}
delete toRemove;
this->length--;
}
}
void removeAll() override {
Node<E>* p = this->first;
while (p != nullptr) {
Node<E>* temp = p->next;
delete p;
if (temp != nullptr) {
p = temp;
} else {
break;
}
}
this->first = nullptr;
this->last = nullptr;
this->length = 0;
}
std::string toString() override {
std::string left = "{";
std::string right = "}";
std::string prop1 = "\"length\": \"" + to_string(this->length).append("\"");
std::string prop2 = "\"elements\": [";
Node<E>* p = this->first;
while (p != nullptr) {
prop2.append("\"")
.append(to_string(p->data))
.append("\"");
if (p->next != nullptr) {
prop2.append(", ");
}
p = p->next;
}
prop2.append("]");
return left.append(prop1)
.append(", ")
.append(prop2)
.append(right)
.append("\n");
}
bool offer(E element) override {
// 将表头作为队头
this->add(0, element);
return true;
}
E peek() override {
// 将表尾作为栈顶和队尾
return this->last->data;
}
E poll() override {
// 将表为作为队尾
E result = this->last->data;
this->removeByIndex(this->length - 1);
return result;
}
void push(E element) override {
// 将表尾作为栈顶
this->add(element);
}
E pop() override {
// 将表尾作为栈顶
E result = this->last->data;
this->removeByIndex(this->length - 1);
return result;
}
};
#endif //DEMO_TEST_LINKED_LIST_H
四、测试
4.1 测试文件——main.cpp
#include <iostream>
#include <ctime>
#include <iostream>
#include "linked_list.h"
using namespace std;
// 测试add()
void init(List<int>* list) {
srand(time(NULL));
for (int i = 0; i < 10; i++) {
list->add(rand() % 100);
}
cout << list->toString() << endl;
list->add(0, 99);
cout << "add(0, 99)\n" << list->toString() << endl;
list->add(5, 999);
cout << "add(5, 999)\n" << list->toString() << endl;
list->add(9, 9999);
cout << "add(9, 9999)\n" << list->toString() << endl;
}
// 测试removeByElement()
void test1(List<int>* list) {
list->removeByElement(99);
list->removeByElement(999);
list->removeByElement(9999);
cout << "removeByElement 99, 999, 9999\n" << list->toString() << endl;
}
// 测试removeByIndex()
void test2(List<int>* list) {
list->removeByIndex(0);
cout << "removeByIndex(0)\n" << list->toString() << endl;
list->removeByIndex(5);
cout << "removeByIndex(5)\n" << list->toString() << endl;
list->removeByIndex(9);
cout << "removeByIndex(9)\n" << list->toString() << endl;
}
// 测试removeAll()
void test3(List<int>* list) {
list->removeAll();
cout << "removeAll()\n" << list->toString() << endl;
}
// 测试set()
void test4(List<int>* list) {
list->set(0, 77);
list->set(5, 777);
list->set(9, 7777);
cout << "set(0, 77), set(5, 777), set(9, 7777)\n" << list->toString() << endl;
}
// 测试indexOf()
void test5(List<int>* list) {
int a = list->indexOf(99);
int b = list->indexOf(999);
int c = list->indexOf(9999);
cout << "indexOf(99)=" << a << ", indexOf(999)=" << b << ", indexOf(9999)=" << c << endl;
}
int main() {
List<int>* list = new LinkedList<int>();
init(list);
// test1(list);
// test2(list);
// test3(list);
// test4(list);
// test5(list);
return 0;
}
4.2 测试截图