前言
写个makefile并且加入asan用来检查内存相关的内容。直接在此目录下make即可。因为我老是控制不好申请和释放。
SRC = $(wildcard *.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SRC))
ALL: test.out
test.out: $(OBJ)
g++ -fsanitize=address $< -lm -o $@ -l pthread
$(OBJ): $(SRC)
g++ -fsanitize=address -c $< -lm -o $@ -l pthread
1 链表
1.1 概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
------百度词条
关于链表的概念就不介绍了,直接看图实战。
由上图可以知道链表头不存数据,非空链表直接指向下一个节点,通过这样串连在一起。
定义节点的结构体
struct Node
{
int val;
Node* next = nullptr;
};
对应的第一个节点赋值和申请
// listNode* p1 = nullptr;
// p1 = new listNode;
Node* p1 = new Node; //上述两行等价与这一行
p1->val = 2;
p1->next = nullptr;
Node* p2 = new Node;
p2->val = 4;
p2->next = nullptr;
然后把两个节点串联起来。
p1->next = p2;
看串连是否成功,写一个打印链表的函数。我这边是把头节点也打印出来,方便理解。
void printList(Node* list)
{
int i = 0;
while (list != nullptr)
{
cout << "[" << i << "]:"<< list->val << " ";
list = list->next;
i++;
}
cout << endl;
}
直接打印
printList(p1);
输出终端结果为:表示串联成功,注意,头节点一般不存储数据。但是它理论是可以存储数据的。
[0]:2 [1]:4
1.2 LIST基础操作
-
尾插
单向链表最常用的插入数据是尾插。
尾插的工作原理是遍历到链表尾部的nullptr然后在这个位置把对应的node插进去
直接看代码实现.插入的时候注意下是list->next的时候为null的判断。
void pushBack(Node* list,int val) { Node* cur = p; Node* newNode = new Node; newNode->val = val; while(cur->next!=nullptr) { cur = cur->next; } cur->next = newNode; }
测试demo为
int main() { Node* p1 = new Node; p1->val = 1; pushBack(p1,2); pushBack(p1,3); pushBack(p1,4); printList(p1); return 0; }
打印输出的结果为
[0]:1 [1]:2
[0]:1 [1]:2 [2]:3
[0]:1 [1]:2 [2]:3 [3]:4
[0]:1 [1]:2 [2]:3 [3]:4
-
头插入
头插顾名思义就是在链表的头部插入数据,将新节点插入到头节点后面即可。
void pushFront(Node* p,int val)
{
if(p == nullptr)
{
p = new Node;
p ->val = val;
p ->next = nullptr;
}
else
{
Node* newNode = new Node;
newNode->val = val;
newNode->next = p->next;
p->next = newNode;
}
}
- 求链表长度
链表长度不算头节点。正常的从头开始遍历计数即可。
int lensList(Node* p)
{
int count = 0;
while (p->next!=nullptr)
{
count++;
p = p->next;
}
return count;
}
-
链表的逆序
void reverse(Node* head)
{
if ((head->next == nullptr )||(head->next->next == nullptr))
{
return;
}
else
{
Node* pCur = head->next;
Node* pNext = pCur->next;
Node* pre = nullptr;
while (pNext)
{
pCur->next = pre;
pre = pCur;
pCur = pNext;
pNext = pCur->next;
}
pCur->next = pre;
head->next = pCur;
return;
}
}
-
删除链表:某个节点作为暂存节点去存,然后去不停的删这个暂存的节点即可。
void deleteList(Node* head) { while (head) { Node* del = head; head = head->next; delete del; } head = nullptr; delete head; }
2 二插树
2.1 概念
struct binaryTree
{
int data;
binaryTree* left;
binaryTree* right;
};
前序遍历二叉树:先输出根节点,再输出左孩子,之后输出右孩子
void preOrder(binaryTree* node)
{
if(node)
{
cout << node->data << " ";
preOrder(node->left);
preOrder(node->right);
}
}
中序遍历二叉树:先输出左孩子,再输出根节点,之后输出右孩子。
void midOrder(binaryTree* node)
{
if(node)
{
midOrder(node->left);
cout << node->data << " ";
midOrder(node->right);
}
}
后序遍历二叉树:先输出左孩子,再输出右还在,在输出根节点
void postOrder(binaryTree* node)
{
if(node)
{
postOrder(node->left);
postOrder(node->right);
cout << node->data << " ";
}
}
前序,中序,后序遍历是根据根节点的位置来定的。
相关demo如下
int main()
{
binaryTree* t1 = new binaryTree;
t1->data = 1;
binaryTree* t2 = new binaryTree;
t2->data = 2;
binaryTree* t3 = new binaryTree;
t3->data = 3;
t1->left = t2;
t1->right = t3;
preOrder(t1);
cout << endl;
midOrder(t1);
cout << endl;
postOrder(t1);
cout << endl;
}