数据结构大体可以分为两个部分:逻辑结构和物理结构。
物理结构大体也可以分为两个部分,即顺序结构和链式存储结构。
而线性结构就是逻辑结构中的一种。
一、线性表介绍
线性表是零个或多个数据元素组成的有限序列,数据元素之间有顺序,数据元素个数有限且类型必须相同。动态数组、链表、栈和队列都属于线性结构。
线性表性质
1.a[0]为线性表的第一个元素,只有一个后继。
2.a[n - 1]为线性表最后一个元素,只有一个前驱。
3.除a[0]和a[n - 1]之外的其他元素,既有前驱,又有后继。
4.线性表能够逐项访问和顺序存储。
二、动态数组
1)分析与设计
头文件DynamicArray.h
#pragma once #includeclass dynamicArray { public: dynamicArray(int capcity); void insert(int pos, void* data); void push_back(void* data); void for_each(void(*MyPrint)(void*));//参数为函数指针,由用户提供 void remove(int pos); void remove(void* value, bool(*MyCompare)(void*, void*)); ~dynamicArray(); private: void** m_pAdder;//维护真实开辟在堆区的指针 int m_capacity;//容量 int m_size;//当前大小 };
设计思路:
1.将二级指针、容量、大小等属性设为私有权限,避免用户直接调用。
2.提供按位置插入和尾插两种插入元素的方式。
3.利用函数重载remove实现按位置删除元素和按值删除元素。
4.提供遍历API,用数提供比较函数即可
5.返回容量、大小等API可根据自身需求考虑是否提供
2)实现
源文件DynamicArray.cpp
#include"DynamicArray.h" dynamicArray::dynamicArray(int capacity) { if (capacity <= 0)return; this->m_pAdder = (void**)new (void*[capacity]); if (this->m_pAdder == nullptr)return; this->m_capacity = capacity; this->m_size = 0; } void dynamicArray::insert(int pos, void* data) { if (this->m_pAdder == nullptr || data == nullptr)return; if (pos<0 || pos>this->m_size) { pos = this->m_size;//若位置无效则进行尾插 } //动态扩展 if (this->m_size == this->m_capacity) { void** newSpace = (void**)new(void*[this->m_capacity * 2]);//每次扩展为原来的两倍 if (newSpace == nullptr)return; memcpy(newSpace, this->m_pAdder, sizeof(void*) * this->m_size); delete[]this->m_pAdder; this->m_pAdder = newSpace; this->m_capacity *= 2; } //插入元素 for (int i = this->m_size - 1; i >= pos; --i)//反向遍历 { this->m_pAdder[i + 1] = this->m_pAdder[i];//看似越界实则并没有,m_capacity > m_size } this->m_pAdder[pos] = data; ++this->m_size; } void dynamicArray::push_back(void* data) { this->insert(this->m_size, data); } void dynamicArray::for_each(void(*MyPrint)(void*)) { if (this->m_pAdder == nullptr || MyPrint == nullptr)return; for (int i = 0; i < this->m_size ; ++i) { MyPrint(this->m_pAdder[i]); } } void dynamicArray::remove(int pos) { if (pos<0 || pos>this->m_size - 1)return; for (int i = pos; i < this->m_size - 1; ++i) { this->m_pAdder[i] = this->m_pAdder[i + 1]; } --this->m_size; } void dynamicArray::remove(void* value, bool(*MyCompare)(void*, void*)) { if (value == nullptr)return; for (int i = 0; i < this->m_size; ++i) { if (MyCompare(this->m_pAdder[i], value)) { this->remove(i); --i; } } } dynamicArray:: ~dynamicArray() { if (this->m_pAdder != nullptr) { delete[]this->m_pAdder; this->m_pAdder = nullptr; } }
三、单链表(企业设计方式)
1)分析与设计
该设计方式与常见的一个数据域、一个指针域的设计方式并相同。
应与用户协定:使用该链表时,自定义数据类型预留4个字节的空间交予链表连接使用。
该方法本质上连接的是用户的数据,而传统版是一个个结点连接,插入时创建新结点并将用户数据拷贝进去。
头文件LinkList.h
#pragma once #includeusing namespace std; class LinkNode { friend class LinkList; private: LinkNode* next; }; class LinkList { public: LinkList(); void insert(int pos, void* data); void push_back(void* data); void for_each(void(*MyPrint)(void*)); void remove(int pos); ~LinkList(); private: LinkNode pHeader; int m_size; };
2)实现
源文件LinkList.cpp
#include"LinkList.h" LinkList::LinkList() { this->pHeader.next = nullptr; this->m_size = 0; } void LinkList::insert(int pos, void* data) { if (data == nullptr)return; if (pos<0 || pos>this->m_size) { pos = this->m_size;//无效位置变为尾插 } LinkNode* NewNode = (LinkNode*)data; LinkNode* pCurrent = &(this->pHeader); for (int i = 0; i < pos; ++i) { pCurrent = pCurrent->next;//找到前驱结点 } //变更指针指向 NewNode->next = pCurrent->next; pCurrent->next = NewNode; ++this->m_size; } void LinkList::push_back(void* data) { this->insert(this->m_size, data); } void LinkList::for_each(void(*MyPrint)(void*)) { LinkNode* node = this->pHeader.next; for (int i = 0; i < this->m_size; ++i) { MyPrint(node); node = node->next; } } void LinkList::remove(int pos) { if (pos<0 || pos>this->m_size - 1)return; LinkNode* pCurrent = &(this->pHeader); for (int i = 0; i < pos; ++i) { pCurrent = pCurrent->next;//找到前驱结点 } LinkNode* pDel = pCurrent->next; pCurrent->next = pDel->next; --this->m_size; } LinkList::~LinkList() { this->pHeader.next = nullptr; this->m_size = 0; }
四、栈(受限线性表)
它的特殊之处在于限制了这个线性表的插入和删除的位置,它始终只在栈顶进行。
可分别使用数组和链表实现栈
1)利用数组实现栈
数组首地址做栈底。栈顶(数组尾部)频繁做出入栈和出栈操作。
对于数组尾部做插入和删除操作效率高。(无需移动其他元素)
头文件StackArray.h
#pragma once #define MAX 1024 #include#include using namespace std; class Stack { public: Stack(); void top_back(void* data);//压栈 void pop_back();//出栈 void* top();//返回栈顶 int size(); bool isEmpty(); ~Stack(); private: void* data[MAX];//指针数组——栈数组 int m_size; };
源文件StackArray.cpp
#include"StackArray.h" Stack::Stack() { this->m_size = 0; memset(this->data, 0, sizeof(void*) * MAX); } void Stack::top_back(void* data) { if (data == nullptr)return; this->data[this->m_size] = data; ++this->m_size; } void Stack::pop_back() { this->data[this->m_size - 1] = nullptr; --this->m_size; } void* Stack::top() { if (this->m_size == 0)return nullptr; return this->data[this->m_size - 1]; } int Stack::size() { return this->m_size; } bool Stack::isEmpty() { if (this->m_size == 0)return true; return false; } Stack::~Stack() { this->m_size = 0; memset(this->data, 0, sizeof(void*) * MAX); }
2)利用单链表实现栈
链表头做栈顶利于频繁地插入删除(无需通过遍历找到尾结点)
头文件StackLink.h
#pragma once class StackNode { friend class StackLink; private: StackNode* next; }; class StackLink { public: StackLink(); void top_back(void* data); void pop_back(); void* top(); int size(); bool isEmpty(); ~StackLink(); private: StackNode pHeader; int m_size; };
源文件StackLink.cpp
#include"StackLink.h" StackLink::StackLink() { this->pHeader.next = nullptr; this->m_size = 0; } void StackLink::top_back(void* data) { if (data == nullptr)return; StackNode* myNode = (StackNode*)data; myNode->next = this->pHeader.next; this->pHeader.next = myNode; ++this->m_size; } void StackLink::pop_back() { StackNode* pDel = this->pHeader.next; this->pHeader.next = pDel->next; --this->m_size; } void* StackLink::top() { if (this->m_size == 0)return nullptr; return this->pHeader.next; } int StackLink::size() { return this->m_size; } bool StackLink::isEmpty() { if (this->m_size == 0) { return true; } return false; } StackLink::~StackLink() { this->pHeader.next = nullptr; this->m_size = 0; }
3)栈的应用——就近匹配
1.算法思想
从第一个字符开始扫描,当遇见普通字符时忽略。当遇见左括号时压入栈中,遇见右括号则弹出栈顶符号进行匹配。
匹配成功,继续识别下一字符。
匹配失败,立即停止,报错。
成功条件:所有字符扫描完且栈为空
失败条件:匹配失败或扫描完毕但栈非空
2.实现
#include#include #include"StackArray.h" using namespace std; bool isLeft(char ch) { return ch == '('; } bool isRight(char ch) { return ch == ')'; } void printError(char* str, string errMsg, char* pos) { cout << "错误信息:" << errMsg << endl; cout << str << endl; int num = pos - str; for (int i = 0; i < num; ++i) { cout << " "; } cout << "~" << endl; } int main() { char* str = (char*)"5 + 5 * (6) + 9 / 3 * 1 - ( 1 + 310"; char* p = str; Stack sk; while (*p != '\0') { if (isLeft(*p)) { sk.top_back(p); } if (isRight(*p)) { if (sk.size() > 0) { sk.pop_back(); } else { printError(str, "右括号没有匹配到对应的左括号", p); } } ++p; } while (sk.size() > 0) { printError(str, "左括号没有匹配到右括号", (char*)sk.top()); sk.pop_back(); } return 0; }
五、队列(受限线性表)
只允许在一端进行插入操作,在另一端进行删除操作
可分别使用数组和链表实现队列
1)队列的顺序存储
数组的首地址做队头或队尾效率相同,本文不做详细介绍
2)利用单链表实现队列
头文件QueueLink.h
#pragma once class QueueNode { friend class QueueLink; private: QueueNode* next; }; class QueueLink { public: QueueLink(); void push_QueueLink(void* data); void pop_QueueLink(); int size(); void* head(); void* tail(); bool isEmpty(); ~QueueLink(); private: QueueNode pHeader; QueueNode* pTail;//用于记录尾结点,不必通过遍历找到尾结点 int m_size; };
源文件QueueLink.cpp
#include"QueueLink.h" QueueLink::QueueLink() { this->m_size = 0; this->pHeader.next = nullptr; this->pTail = &this->pHeader; } void QueueLink::push_QueueLink(void* data) { if (data == nullptr)return; QueueNode* myNode = (QueueNode*)data; this->pTail->next = myNode; myNode->next = nullptr; this->pTail = myNode; ++this->m_size; } void QueueLink::pop_QueueLink() { if (this->m_size == 0)return; if (this->m_size == 1) { this->pHeader.next = nullptr; this->pTail = &this->pHeader; } else { QueueNode* pDel = this->pHeader.next; this->pHeader.next = pDel->next; } --this->m_size; } int QueueLink::size() { return this->m_size; } void* QueueLink::head() { return this->pHeader.next; } void* QueueLink::tail() { return this->pTail; } bool QueueLink::isEmpty() { if (this->m_size == 0)return true; return false; } QueueLink::~QueueLink() { this->m_size = 0; this->pHeader.next = nullptr; this->pTail = &this->pHeader; }
到此这篇关于C++线性表深度解析之动态数组与单链表和栈及队列的实现的文章就介绍到这了,更多相关C++动态数组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!