常用数据结构的实现及其重要操作
线性结构
1. 线性表
a. 顺序表(定长)
b. 链表(变长)
以下给出链表的一些基本操作:
Ⅰ. 链表倒置(建议画图理解)
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL; // 注意这里的置空
while (head) {
ListNode* tmp = head->next;
head->next = pre;
pre = head;
head = tmp;
}
return pre;
}
Ⅱ. 判断链表是否有圈 (双指针)
bool hasCycle(ListNode* head) {
if (head == NULL || !head->next)
return -1;
if (head->next->next == head)
return true;
bool flag = false;
ListNode* fast = head;
ListNode* slow = head;
while (fast->next->next && slow->next) { // 注意这里的边界判断,防止越界
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
flag = true;
break;
}
}
return flag ? true : -1;
}
Ⅲ. 合并两个有序链表
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// 非递归解法
ListNode* preHead = new ListNode(-1);
ListNode* pre = preHead;
while (l1 && l2) {
if (l1->val < l2->val)
{
pre->next = l1;
l1 = l1->next;
}
else
{
pre->next = l2;
l2 = l2->next;
}
pre = pre->next;
}
pre->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// 递归解法
if (l1 == NULL)
return l2;
if (l2 == NULL)
return l1;
if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
Ⅳ. 删除链表倒数第n个元素
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* fast = head;
ListNode* slow = head;
ListNode* prev = head;
for (int i = 0; i < n - 1; i++) // 双指针思想:让快指针先走 n-1 步
fast = fast->next;
while (fast) {
prev = slow;
slow = slow->next;
fast = fast->next;
}
if (slow->next == NULL) { // 待删结点为尾结点
prev->next = NULL;
}
else {
prev->next = slow->next; // 待删结点不为尾结点
}
delete slow;
return head;
}
Ⅴ. 返回链表的中间结点
ListNode* middleNode(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next) { // 注意此处的判断条件有两个(分别对应偶数和奇数)且顺序不可颠倒
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
2. 栈(限制访问端口的线性表)
a. 顺序栈的实现
template<class T>
class stack {
private:
int maxSize; // 栈的最大值
int top; // 栈顶位置
T* st; // 存放栈元素的数组
public:
stack(int size) :maxSize(size), top(-1) { st = new T[size]; } // 给定长度的实例
stack() :top(-1){}
~stack() { delete[] st; }
void clear() { // 清空栈的内容
top = -1;
}
bool Push(const T item) { // 入栈操作
if (top == maxSize - 1) {
cout << "No place for a new item!" << endl;
return false;
}
else {
st[++top] = item;
return true;
}
}
bool Push_f(const T item) { // 若执意入栈,则先扩大size
if (top == maxSize - 1) {
T* newSt = new T[maxSize * 2];
for (int i = 0; i <= top; i++) {
newSt[i] = st[i];
}
maxSize = maxSize * 2;
st = newSt;
}
st[++top] = item;
return true;
}
bool Pop(T& item) { // 出栈操作
if (top == -1) {
cout << "Stack is empty!" << endl;
return false;
}
else {
item = st[top--];
return true;
}
}
bool Top(T& item) { // 取栈顶元素
if (top == -1) {
cout << "Stack is empty!" << endl;
return false;
}
else {
item = st[top];
return true;
}
}
};
b.链式栈的实现(待更新...)
3. 队列(限制访问端口的线性表)
a.顺序队列的实现
说明: 为解决队列插入时的”假溢出“现象,我们采用取余的方法构造逻辑上的环形队列,即增加一个空间,
使得下标 x 的后继位置为 (x + 1) % maxSize , 此处的 maxSize 为 实际的元素个数加一
简而言之,两个重要的判断条件是:
A. 队列满时 (rear + 1) / maxsize == front
B. 队列空时 rear == front
template<class T>
class Queue {
private:
int maxSize; // 队列数组的大小
int front; // 对头元素下标
int rear; // 队尾元素下标
int* queue; // 存放队列元素的数组
public:
Queue(int size) {
maxSize = size + 1; // 注意此处多出一个空间, 区分队列空与满(待说明...)
queue = new T[size + 1];
front = rear = 0;
}
~Queue() { delete[]queue; } // 析构函数
void clear() { // 清空队列
front = rear; // 注意此处的条件:首尾相遇意味着队列为空
}
bool EnQueue(const T item) {
if ((rear + 1) % maxSize == front) {
cout << "Queue is full!" << endl;
return false;
}
else {
queue[rear] = item; // 正常情况下queue[rear]没有值,是待填充的
rear = (rear + 1) % maxSize; // 注意 ++ 操作的改变
return true;
}
}
bool DeQueue(T& item) { // 返回并删除队首
if (front == rear) {
cout << "Queue is empty!" << endl;
return false;
}
item = queue[front];
front = (front + 1) % maxSize; // 注意 ++ 操作的改变
return true;
}
bool GetFront(T& item) { // 返回但不删除队首
if (front == rear) {
cout << "Queue is empty!" << endl;
return false;
}
item = queue[front];
return true;
}
};
b.链式队列的实现(待补充...)
4. 字符串
KMF模式匹配
1. Next 数组的实现
2. 匹配算法主体的实现