本文共 14031 字,大约阅读时间需要 46 分钟。
单链表设计要点:
A、类模板,通过头结点访问后继结点。 B、定义内部结点类型,用于描述链表中的结点的数据域和指针域。C、实现线性表的关键操作struct Node:public Object { T value;//数据域 Node* next;//指针域 };
mutable Node m_header;
templateclass LinkedList:public List {protected: struct Node:public Object { T value;//数据域 Node* next;//指针域 }; Node m_header; int m_length;public: LinkedList(); virtual ~LinkedList(); bool insert(int index, const T& value); bool remove(int index); bool set(int index, const T& value); bool get(int index, T& value)const; int length()const; int find(const T& value)const; void clear();};
在目标位置处插入数据元素流程如下:
A、从头结点开始,提供current指针定位到目标位置B、从堆空间申请新的Node结点C、将新结点插入目标位置,并连接相邻结点的逻辑关系。bool insert(int index, const T& value) { //判断目标位置是否合法 bool ret = (0 <= index) && (index <= m_length); if(ret) { //创建新的结点 Node* node = new Node(); if(node != NULL) { //初始化当前结点为头结点 Node* current = &m_header; //遍历到目标位置 for(int i = 0; i < index; i++) { current = current->next; } //修改插入结点的值 node->value = value; //将当前位置的下一个结点作为插入结点的下一个结点 node->next = current->next; //将要插入结点作为当前结点的下一个结点 current->next = node; m_length++;//链表结点长度加1 } else { THROW_EXCEPTION(NoEnoughMemoryException, "No enough memmory..."); } } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter index is invalid..."); } return ret; }
在目标位置删除数据元素的流程:
A、从头结点开始,通过current指针定位到目标位置B、使用toDel指针指向要被删除的结点C、删除结点,并连接相邻结点的逻辑关系bool remove(int index) { //判断目标位置是否合法 bool ret = (0 <= index) && (index < m_length); if(ret) { //当前结点指向头结点 Node* current = &m_header; //遍历到目标位置 for(int i = 0; i < index; i++) { current = current->next; } //使用toDel指向要删除的结点 Node* toDel = current->next; //将当前结点的下一个结点指向要删除结点的下一个结点 current->next = toDel->next; m_length--;//链表结点长度减1 delete toDel;//释放要删除的结点的堆空间 } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter inde is invalid..."); } return ret; }
设置目标结点的值的流程如下:
A、从头结点开始,通过current指针定位到目标位置B、修改目标结点的数据域的值bool set(int index, const T& value) { //判断目标位置是否合法 bool ret = (0 <= index) && (index < m_length); if(ret) { //将当前结点指向头结点 Node* current = &m_header; //遍历到目标位置 for(int i = 0; i < index; i++) { current = current->next; } //修改目标结点的数据域的值 current->next->value = value; } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter inde is invalid..."); } return ret; }
bool get(int index, T& value)const { //判断目标位置是否合法 bool ret = (0 <= index) && (index < m_length); if(ret) { //当前结点指向头结点 Node* current = &m_header; //遍历到目标位置 for(int i = 0; i < index; i++) { current = current->next; } //获取目标结点的数据域的值 value = current->next->value; } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter inde is invalid..."); } return ret; } //重载版本 T get(int index)const { T ret; get(index, ret); return ret; }
int length()const { return m_length; }
void clear(){ //遍历删除结点 while(m_header.next) { //要删除的结点为头结点的下一个结点 Node* toDel = m_header.next; //将头结点的下一个结点指向删除结点的下一个结点 m_header.next = toDel->next; delete toDel;//释放要删除结点 } m_length = 0;}
struct Node:public Object{ T value;//数据域 Node* next;//指针域}; mutable Node m_header;
由于单链表在构建时必须先创建头结点,头结点在创建时必须调用具体类型的构造函数,如果具体类型的构造函数抛出异常,则单链表对象将构建失败,并会传递具体类型构造函数的异常。
class Test{public: Test() { throw 0; }};int main(){ LinkedListl; return 0;}
因此,为了确保模板类的健壮性,需要对头结点的创建进行优化,即在创建单链表对象时不调用具体类型的构造函数。
mutable struct:public Object { char reserved[sizeof(T)];//占位空间 Node* next; }m_header;
创建的匿名的头结点m_header的内存布局与Node对象的内存布局完全一样,并且不会调用具体类型T的构造函数。
单链表的操作中经常会定位到目标位置,因此可以将此部分代码独立构建一个函数。
Node* position(int index)const { //指针指向头结点 Node* ret = reinterpret_cast(&m_header); //遍历到目标位置 for(int i = 0; i < index; i++) { ret = ret->next; } return ret; }
为List模板类增加一个find操作:
virtual int find(const T& value)const = 0;顺序存储结构的线性表SeqList模板类的find实现如下:int find(const T& value)const { int ret = -1; //遍历线性表 for(int i = 0; i < m_length; i++) { //如果找到元素,退出循环 if(m_array[i] = value) { ret = i; break; } } return ret; }
链式存储结构的线性表的find操作如下:
int find(const T& value)const { int ret = -1; //指向头结点 Node* node = m_header.next; int i = 0; while(node) { //找到元素,退出循环 if(node->value == value) { ret = i; break; } else { node = node->next; i++; } } return ret; }
通常遍历链表的方法时间复杂度为O(n^2)
for(int i = 0; i < ll.length(); i++){ ll.get(i); }
通过使用游标的方法将遍历链表的时间复杂度优化为O(n):
A、在单链表的内部定义一个游标(Node* m_current)B、遍历开始前将游标指向位置为0的数据元素C、获取游标指向的数据元素D、通过结点中的next指针移动游标提供一组遍历相关成员函数,以线性时间复杂度遍历链表:bool move(int pos, int step = 1) { bool ret = (0 <= pos) && (pos < m_length) && (0 < step); if(ret) { m_current = position(pos); m_step = step; } return ret; } bool end() { return m_current == NULL; } T current() { if(!end()) { return m_current->value; } else { THROW_EXCEPTION(InvalidOperationException, "Invalid Operation..."); } } bool next() { int i = 0; while((i < m_step) && (!end())) { m_current = m_current->next; i++; } return (i == m_step); }
使用游标遍历链表的方法:
for(ll.move(0); !ll.end(); ll.next()){ cout << ll.current() << endl; }
单链表反转有递归和非递归两种算法。
单链表翻转的递归实现:Node* reverse(Node* list) { Node* ret = NULL; //如果单链表为空 if(list == NULL || list->next == NULL) { ret = list; } else { Node* guard = list->next; ret = reverse(list->next); guard->next = list; list->next = NULL; } return ret; }
单链表翻转的非递归实现:
Node* reverse(Node *header) { Node* guard = NULL;//链表翻转后的头结点 Node* current = reinterpret_cast(header);//当前结点 Node* prev = NULL;//前一结点 while(current != NULL) { Node* pNext = current->next;//下一结点 if(NULL == pNext)//如果是单结点链表 { guard = current; } current->next = prev;//当前结点的下一个结点指向前一结点,实现翻转 prev = current;//将前一结点移到当前结点位置 current = pNext;//将当前结点后移 } return guard; }
templateclass LinkedList:public List { protected: struct Node:public Object { T value;//数据域 Node* next;//指针域 }; mutable struct:public Object { char reserved[sizeof(T)];//占位空间 Node* next; }m_header; int m_length; int m_step; Node* m_current; protected: Node* position(int index)const { //指针指向头结点 Node* ret = reinterpret_cast (&m_header); //遍历到目标位置 for(int i = 0; i < index; i++) { ret = ret->next; } return ret; } public: LinkedList() { m_header.next = NULL; m_length = 0; m_step = 1; m_current = NULL; } virtual ~LinkedList() { clear(); } bool insert(int index, const T& value) { //判断目标位置是否合法 bool ret = (0 <= index) && (index <= m_length); if(ret) { //创建新的结点 Node* node = createNode(); if(node != NULL) { //当前结点定位到目标位置 Node* current = position(index); //修改插入结点的值 node->value = value; //将当前位置的下一个结点作为插入结点的下一个结点 node->next = current->next; //将要插入结点作为当前结点的下一个结点 current->next = node; m_length++;//链表结点长度加1 } else { THROW_EXCEPTION(NoEnoughMemoryException, "No enough memmory..."); } } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter index is invalid..."); } return ret; } bool insert(const T& value) { return insert(m_length, value); } bool remove(int index) { //判断目标位置是否合法 bool ret = (0 <= index) && (index < m_length); if(ret) { //当前结点指向头结点 Node* current = position(index); //使用toDel指向要删除的结点 Node* toDel = current->next; //将当前结点的下一个结点指向要删除结点的下一个结点 current->next = toDel->next; m_length--;//链表结点长度减1 destroy(toDel);//释放要删除的结点的堆空间 } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter inde is invalid..."); } return ret; } bool set(int index, const T& value) { //判断目标位置是否合法 bool ret = (0 <= index) && (index < m_length); if(ret) { //将当前结点指向头结点 Node* current = position(index); //修改目标结点的数据域的值 current->next->value = value; } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter inde is invalid..."); } return ret; } bool get(int index, T& value)const { //判断目标位置是否合法 bool ret = (0 <= index) && (index < m_length); if(ret) { //当前结点指向头结点 Node* current = position(index); //遍历到目标位置 //获取目标结点的数据域的值 value = current->next->value; } else { THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter inde is invalid..."); } return ret; } //重载版本 T get(int index)const { T ret; if(get(index, ret)) return ret; } int length()const { return m_length; } int find(const T& value)const { int ret = -1; //指向头结点 Node* node = m_header.next; int i = 0; while(node) { //找到元素,退出循环 if(node->value == value) { ret = i; break; } else { node = node->next; i++; } } return ret; } void clear() { //遍历删除结点 while(m_header.next) { //要删除的结点为头结点的下一个结点 Node* toDel = m_header.next; //将头结点的下一个结点指向删除结点的下一个结点 m_header.next = toDel->next; m_length--; destroy(toDel);//释放要删除结点 } } bool move(int pos, int step = 1) { bool ret = (0 <= pos) && (pos < m_length) && (0 < step); if(ret) { m_current = position(pos)->next; m_step = step; } return ret; } bool end() { return m_current == NULL; } T current() { if(!end()) { return m_current->value; } else { THROW_EXCEPTION(InvalidOperationException, "Invalid Operation..."); } } bool next() { int i = 0; while((i < m_step) && (!end())) { m_current = m_current->next; i++; } return (i == m_step); } virtual Node* createNode() { return new Node(); } virtual void destroy(Node* node) { delete node; } };
长时间使用单链表频繁增加和删除数据元素时,堆空间会产生大量内存碎片,导致系统运行缓慢。
静态单链表的实现要点:
A、通过类模板实现静态单链表B、在类中定义固定大小的空间C、重写create、destroy函数,改变内存的分配和释放方式D、在Node类中重载operator new操作符,用于在指定内存上创建对象。templateclass StaticLinkedList:public LinkedList { protected: typedef typename LinkedList ::Node Node; struct SNode:public Node { //重载new操作符 void* operator new(unsigned int size, void* loc) { return loc; } }; unsigned char m_space[N*sizeof(Node)];//顺序存储空间 bool m_used[N];//空间可用标识 public: StaticLinkedList() { for(int i = 0; i < N; i++) { m_used[i] = false; } } Node* createNode() { SNode* ret = NULL; for(int i = 0; i < N; i++) { if(!m_used[i]) { ret = reinterpret_cast (m_space) + i; ret = new(ret) SNode(); m_used[i] = true; break; } } return ret; } void destroy(Node* node) { SNode* space = reinterpret_cast (m_space); SNode* pn = dynamic_cast (node); for(int i = 0; i < N; i++) { if(pn == space + i) { m_used[i] = false; pn->~SNode(); } } } int capacty() { return N; } };
静态单链表拥有单链表的所有操作,在预留的顺序存储空间中创建结点对象,适合于最大数据元素个数固定需要频繁增加和删除数据元素的场合。
转载于:https://blog.51cto.com/9291927/2063164