一、概述
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 測試截圖