1.顺序表
(1)顺序表的特点:
(2)顺序表与内置数组的区别:
- 1)顺序表在数组的基础上封装了一些方便的操作
- 2)顺序表的创建是在堆上申请内存,而传统的直接声明的数组是在栈上申请内存
(3)实现:
Line_Table_ADT.h
#pragma once //有的编译器实现的保证.h文件只被编译一次,但要是写跨平台的程序,还是用ifndef,endif
#ifndef LINE_TABLE_ADT_H
#define LINE_TABLE_ADT_H
//抽象基类
template <typename T>
class List
{
public:
typedef T Element_Type;
typedef T* Element_Pointer;
typedef T& Element_Reference;
typedef const T& Const_Element_Reference;
private:
void operator= (const List&) {}
List(const List&) {}
public:
//构造函数
List() {}
//析构函数
virtual ~List() {}
//纯虚函数子类必须去继承它吗?
//清空线性表
virtual void clear() = 0;
//在当前位置插入
virtual void insert(Const_Element_Reference element) = 0;
//在线性表末尾添加
virtual void append(Const_Element_Reference element) = 0;
//移除当前位置的元素
virtual Element_Type remove() = 0;
//当前位置的前一个元素
virtual void prev() = 0;
//当前位置的下一个元素
virtual void next() = 0;
//线性表的长度
virtual int length() const = 0;
//当前位置
virtual int currPos() const = 0;
//当前位置元素的值
virtual Const_Element_Reference getValue() = 0; //思考返回一个常引用有什么好处?
//移动到指定位置
virtual void moveToPosition(int position) = 0;
};
#endif
Array_List.h
#pragma once
//模板类的函数声明和定义应该放在同一个文件
#include "Line_Table_ADT.h"
#include <string>
#define ARRAY_DEFAULT_SIZE 100
using namespace std;
template <typename T>
class Array_List:public List<T>
{
public:
//数据类型表
typedef T Value_Type;
typedef Value_Type* List_Array;
typedef const T& Const_Value_Reference;
private:
int array_MaxSize;//顺序表可以存放元素的总空间
int list_Size; //顺序表已有元素的长度
int current_position; //当前元素位置
List_Array list_Array;//顺序表封装的数组
//满足某种异常条件,抛出异常,打印错误信息
void judge_OutOfRange(bool condition, const string& printInfo)
{
try
{
if (condition)
{
throw condition;
}
}
catch (bool)
{
cerr << printInfo << endl;
exit(1);
}
}
public:
//默认构造函数
Array_List(int size = ARRAY_DEFAULT_SIZE)
{
array_MaxSize = size;
list_Size = current_position = 0;
list_Array = new Value_Type[array_MaxSize];
}
//析构函数
~Array_List()
{
delete[] list_Array;
}
//清空顺序表
void clear() override
{
//先释放原先申请空间,再申请新的空间
delete[] list_Array;
list_Size = current_position = 0;
list_Array = new Value_Type[array_MaxSize];
}
//插入
void insert(Const_Value_Reference value) override
{
//插入之前判断数组是否已经满了
judge_OutOfRange(list_Size >= array_MaxSize, "List capacity exceeded");
//将当前位置及后续元素后移一位
for (int i=current_position;i< list_Size;i++)
{
list_Array[i + 1] = list_Array[i];
}
//将值插入当前指针指向的位置
list_Array[current_position] = value;
list_Size++; //关于前置和后置++操作效率问题?两个都能用的情况下应该用前置?
}
//末尾添加
void append(Const_Value_Reference value) override
{
judge_OutOfRange(list_Size >= array_MaxSize, "List capacity exceeded");
list_Array[list_Size] = value;
list_Size++;
}
//删除
Value_Type remove() override
{
judge_OutOfRange(list_Size <=0, "List capacity exceeded");
Value_Type temp = list_Array[current_position];
//直接将当前位置的元素均向前移动一位
for (int i = current_position; i < list_Size-1; i++)
{
list_Array[i] = list_Array[i+1];
}
list_Size--;
return temp;
}
//前一个元素
void prev() override
{
judge_OutOfRange(current_position<=0, "Current Position is head of List!");
current_position--;
}
//下一个元素
void next() override
{
judge_OutOfRange(current_position >= list_Size, "Current Position is tail of List!");
current_position++;
}
//线性表的长度
int length() const override
{
return list_Size;
}
//线性表的当前位置
int currPos() const override
{
return current_position;
}
//当前位置元素的值
Const_Value_Reference getValue() const override
{
return list_Array[current_position];
}
//移动到指定位置
void moveToPosition(int position) override
{
current_position = position;
}
};
main.cpp
#include <iostream>
#include "Array_List.h"
using namespace std;
void print(Array_List<int>& array)
{
array.moveToPosition(0);
while(array.currPos() != array.length())
{
cout << array.getValue() << endl;
array.next();
}
array.prev();
}
int main()
{
Array_List<int> test_array_list;
cout << "测试append函数" << endl;
test_array_list.append(5);
test_array_list.append(100);
test_array_list.append(300);
test_array_list.append(400);
test_array_list.append(500);
print(test_array_list);
cout << "测试remove函数" << endl;
cout << "被删除的元素的值为:" << test_array_list.remove() << endl;
print(test_array_list);
cout << "测试insert函数" << endl;
test_array_list.moveToPosition(3);
test_array_list.insert(25);
print(test_array_list);
cout << "测试prev、next、currPos" << endl;
cout << "当前指针位置为:" << test_array_list.currPos() << endl;
test_array_list.prev();
test_array_list.prev();
cout << "前移两次指针位置为:" << test_array_list.currPos() << endl;
test_array_list.next();
cout << "后移一次指针位置为:" << test_array_list.currPos() << endl;
cout << "测试length" << endl;
cout << "当前顺序表的长度为:" << test_array_list.length() << endl;
}
运行结果:
2.单链表
(1)链表的基本特点
(1)链表和数组都是一种线性结构
(2)数组的空间是连续的,而链表的空间不能保证连续,为临时分配
(3)分类
按连接方向分类:
单链表:只能通过next指针寻找下一个节点
双链表:可以通过prev指针寻找上一个节点,通过next指针寻找下一个节点
按照有环和无环分类:
普通链表:无环
循环链表:尾节点的next指针指向头部节点,而对于循环双链表来说,还要用头部节点的prev指针指向尾节点
(4)链表问题代码实现的关键点:
a.链表调整函数的返回值,根据要求往往是节点类型,因为链表调整中很多时候会改变头部,如果头节点换了,自然要返回一个新头部
b.在调整链表的时候注意哪些指针变化了,最好采用画图的方式理清逻辑,看修改了哪些指针,同时可能我们需要预先把前后变化的节点保存下来,避免后续断线找不到后面节点的情况
c.对边界节点的处理,比如说头节点,尾节点,以及空节点的处理,不要总假设指针有意义
(5)链表插入和删除的注意事项:
a.特殊处理链表为空或者链表长度为1的情况
b.注意头尾节点和空节点需要特殊考虑
(2)实现
Line_Table_ADT.h
#pragma once
#ifndef LINE_TABLE_ADT_H
#define LINE_TABLE_ADT_H
//抽象基类
template <typename T>
class List
{
public:
typedef T Value_Type;
typedef T* Value_Pointer;
typedef T& Value_Reference;
typedef const T& Const_Value_Reference;
private:
void operator= (const List&) {}
List(const List&) {}
public:
//构造函数
List() {}
//析构函数
virtual ~List() {}
//纯虚函数子类必须去继承它吗?
//清空线性表
virtual void clear() = 0;
//在当前位置插入
virtual void insert(Const_Value_Reference element) = 0;
//在线性表末尾添加
virtual void append(Const_Value_Reference element) = 0;
//移除当前位置的元素
virtual Value_Type remove() = 0;
//当前位置的前一个元素
virtual void prev() = 0;
//当前位置的下一个元素
virtual void next() = 0;
//线性表的长度
virtual int length() const = 0;
//当前位置
virtual int currPos() const = 0;
//当前位置元素的值
virtual Const_Value_Reference getValue() = 0; //思考返回一个常引用有什么好处?
//移动到指定位置
virtual void moveToPosition(int position) = 0;
};
#endif
Single_Link_List_Node.h
#pragma once
#ifndef SINGLE_LINK_LIST_NODE
#define SINGLE_LINK_LIST_NODE
template <typename T>
class Single_Link_List_Node
{
public:
typedef T Value_Type;
typedef T* Value_Pointer;
typedef T& Value_Reference;
typedef const T& Const_Value_Reference;
typedef Single_Link_List_Node Node;
typedef Single_Link_List_Node* Node_Pointer;
typedef const Single_Link_List_Node& Const_Node_Reference;
typedef Single_Link_List_Node* Next_Pointer;
typedef Node* Node_Pool;
private:
static Node_Pool node_Pool; //声明为static变量,让所有对象共享
public:
Value_Type value; //结点存储的值
Next_Pointer next; //结点保存的指向下一个结点的指针
//结点构造函数,创建结点,对应C语言实现的create函数
Single_Link_List_Node(Const_Value_Reference m_value, Next_Pointer m_next = nullptr)
:value(m_value), next(m_next)
{
}
//默认构造函数
//为头指针,尾指针,当前指针的定义提供不用没有值的构造方法
Single_Link_List_Node(Next_Pointer m_next = nullptr)
{}
//重载new运算符
void* operator new(size_t)
{
//内存池中没有申请到结点内存时,调用全局的new申请内存直接返回
if (node_Pool == NULL)
return ::new Node;
//内存池中有结点时
Node* temp = node_Pool;
//从内存池中申请后,内存池中结点减少,即内存池首地址的指向下一个结点区域
node_Pool = node_Pool->next;
return temp;
}
//重载delete运算符
void operator delete(void* free_node)
{
//释放结点内存直接将结点释放到内存池中
//每释放一个都插入到内存池的头部
//让释放的结点指向内存池首地址
((Node*)free_node)->next = node_Pool;
//让内存池的首地址指向释放的结点
node_Pool = (Node*)free_node;
}
bool operator==(Const_Node_Reference node)
{
if (value == node.value && next == node.next)
return true;
}
};
//类的静态变量的初始化
template <typename T>
Single_Link_List_Node<T>* Single_Link_List_Node<T>::node_Pool = nullptr;
#endif
Single_Link_List.h
#pragma once
#include "Line_Table_ADT.h"
#include "Single_Link_List_Node.h"
#include <string>
#include <iostream>
template <typename T>
class Single_Link_List:public List<T>
{
public:
typedef Single_Link_List_Node<T> Node;
typedef Single_Link_List_Node<T>* Head_Type;
typedef Single_Link_List_Node<T>* Tail_Type;
typedef Single_Link_List_Node<T>* Current_Type;
typedef T Value_Type;
typedef const T& Const_Value_Reference;
private:
Head_Type head;
Tail_Type tail;
Current_Type curr;
int list_Length;
//初始化的函数,只用于此类,不提供对外接口
void init()
{
head = tail = curr = new Node;
list_Length = 0;
}
void removeAll()
{
while (head != nullptr)
{
curr = head;
head = head->next;
delete curr;
}
}
//满足某种异常条件,抛出异常,打印错误信息
void judge_OutOfRange(bool condition, const std::string& printInfo)
{
try
{
if (condition)
{
throw condition;
}
}
catch (bool)
{
std::cerr << printInfo << std::endl; //为什么不要在.h文件中使用using namespace std?
exit(1);
}
}
public:
Single_Link_List()
{
init();
}
~Single_Link_List() //这里析构函数其实还是虚函数,因为它的父类是
{
removeAll();
}
void clear() override
{
removeAll();
init();
}
//默认删除和插入操作都是对当前结点的下一个结点操作,这样写方便操作
void insert(Const_Value_Reference value) override
{
Node* temp = curr->next;
curr->next = new Node; //使用operator new分配内存
::new(curr->next)Node(value,temp);
if (curr == tail) //记得判断尾节点的特殊情况
tail = curr->next;
list_Length++;
}
void append(Const_Value_Reference value) override
{
tail->next = new Node(value, nullptr);
tail = tail->next;
list_Length++;
}
Value_Type remove() override
{
//判断链表是否为空
judge_OutOfRange((curr->next == nullptr), "Link_List is none!");
Value_Type remove_value = curr->next->value;
//移除一个结点:让当前结点的下一个结点的指针,指向下一个结点的下一个结点,同时释放当前下一个结点
Node* temp = curr->next;
if (tail == curr->next)
tail = curr;
curr->next = curr->next->next;
delete temp;
list_Length--;
return remove_value;
}
void prev() override
{
if (curr == head)
return;
Node* temp=head;
while (temp->next != curr)
temp = temp->next;
curr = temp;
}
void next() override
{
if (curr != tail)
curr = curr->next;
}
int length() const
{
return list_Length;
}
Const_Value_Reference getValue() override
{
//判断链表是否为空
judge_OutOfRange((curr->next == nullptr), "Link_List is none!");
return curr->next->value;
}
int currPos()const override
{
Node* temp = head;
int i;
for (i = 0; temp != curr; i++)
{
temp = temp->next;
}
return i;
}
void moveToPosition(int position) override
{
judge_OutOfRange((position<0 || position>=list_Length),"Position is out of range!");
curr = head;
for (int i = 0; i < position; i++)
curr = curr->next;
}
};