前言
接触csdn的第一篇博客,用来记录对数据结构与算法这门课的所学与收获。同时也是第一次尝试Markdown编辑。
ADT
抽象数据类型(Abstract Data Type,ADT)
通常是对数据的某种抽象,定义了数据的取值范围及其结构形式,以及对数据操作的集合
特征
1.数据抽象
2.数据封装
3.继承性
4.多态性
在接下来的具体实现中,所有的顺序实现与链式实现的类将继承自一个拥有许多纯虚函数的抽象类,例如AStack(顺序栈)与LStack(链式栈)继承自Stack类,此处以Stack为例:
template<typename>
class Stack
{
private:
void operator =(const Stack&)() //复制运算符重载
Stack(const Stack&){} //复制构造函数
public:
Stack() {}
virtual ~Stack() {}
virtual void clear() = 0;
virtual void push(const E& it) = 0;
virtual E pop() = 0;
virtual const E& topValue()const = 0;
virtual int length()const = 0;
};
可以看到,类中所含函数多有virtual。
C++的隐藏规则:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
即virtual的目的:使子类中同名同参数函数可继承基类不被隐藏
虚函数与纯虚函数的区别在于是否限定函数值 = 0
虚函数在子类中可以被override(覆盖)、overload (重载 )
拥有纯虚函数的类称为抽象类,抽象类不能被实例化.
纯虚函数被继承后为虚函数
纯虚函数在类(父类)中只提供声明,不提供实现,实现由子类去完成
结点类
顺序结构中一般以数组为基础
而链式结构中以结点为基础
结点是包含一个元素和指向下一个结点的指针的类
template <typename E> class Link
{
public:
E element; // 结点对应的值
Link* next; // 指向下一个结点的指针
Link(const E& elemval, Link* nextval = NULL)
{
element = elemval; next = nextval;
}
Link(Link* nextval = NULL) { next = nextval; }
};
线性表
由元素组成的有限且有序的数列
关键设计:对当前位置(curr)的支持
ADT:
template <typename E>
class List
{
private:
void operator = (const List&){}
List(const List&){}
public:
List(){}
virtual ~List(){}
virtual void clear() = 0;
virtual void insert(const E& item) = 0; //在当前位置插入
virtual void append(const E& item) = 0; //在末尾插入
virtual E remove() = 0;
virtual void moveToStart() = 0; //以下五个方法为对栅栏(curr)的移动
virtual void moveToEnd() = 0;
virtual void prev() = 0;
virtual void next() = 0;
virtual void moveToPos(int pos) = 0;
virtual int length() const = 0;
virtual int currPos()const = 0;
virtual const E& getValue()const = 0;
};
表的遍历方法:
for (L.moveToStart(); L.currPos() < L.length(); L.next())
{
it = L.getValue();
dosomething(it); //自定义的对获取值的操作
}
A.顺序实现
顺序表基于数组,与数组的操作大致相同,除了插入和删除的时间复杂度为n外,其余均为常数级,但正是由于插入和删除操作需要移动当前位置以右的左右元素,较为麻烦,故而有链式实现
子类新增的私有成员:
private:
int maxSize;
int listSize;
int curr;
E* listArray;
公有的成员函数实现
public:
AList(int size = 10)
{
maxSize = size;
listSize = curr = 0;
listArray = new E[maxSize];
}
~AList()
{
delete[] listArray;
}
void clear()
{
delete[] listArray;
listSize = curr = 0;
listArray = new E[maxSize];
}
void insert(const E& it)
{
assert(listSize < maxSize);
for (int i = listSize; i > curr; i--)
{
listArray[i] = listArray[i - 1];
}
listArray[curr] = it;
listSize++;
}
void append(const E& it)
{
assert(listSize < maxSize);
listArray[listSize++] = it;
}
E remove()
{
assert((curr >= 0) && (curr < listSize));
E it = listArray[curr];
for (int i = curr; i < listSize - 1; i++)
{
listArray[i] = listArray[i + 1];
}
listSize--;
return it;
}
void moveToStart() { curr = 0; }
void moveToEnd() { curr = listSize; }
void prev() { if (curr > 0)curr--; }
void next() { if (curr < listSize)curr++; }
int length() const { return listSize; }
int currPos() const { return curr; }
void moveToPos(int pos)
{
assert((pos >= 0) && (pos <= listSize));
curr = pos;
}
const E& getValue() const
{
assert((curr >= 0) && (curr < listSize));
return listArray[curr];
}
B.链式实现
链表以结点为依托,如下图,每一个结点包含一个元素与指向下一个结点的指针
head 指向一个不储存值的头部结点
tail 指向最后一个储存值的结点
curr 指向当前结点的下一个结点
若curr = head,则指向的元素为20
若curr = tail,则指向空结点
子类新增的私有成员:
private:
Link<E>* head; // 指向表头的指针
Link<E>* tail; // 指向表尾的指针
Link<E>* curr; // 指向当前位置的指针
int cnt; // 表长
// 初始化
void init()
{
curr = tail = head = new Link<E>;
cnt = 0;
}
// 清除表
void removeall()
{
while (head != NULL)
{
curr = head;
head = head->next;
delete curr;
}
}
成员函数实现:
public:
Llist(int size = 10) { init(); }
~Llist() { removeall(); }
void clear() { removeall(); init(); }
void insert(const E& it)
{
curr->next = new Link<E>(it, curr->next);
if (tail == curr)tail = curr->next;
cnt++;
}
void append(const E& it)
{
tail->next = new Link<E>(it, NULL);
tail = tail->next;
cnt++;
}
E remove()
{
assert(curr->next != NULL);
E it = curr->next->element;
Link<E>* itemp = curr->next;
if (tail == curr->next)tail = curr;
curr->next = curr->next->next;
delete itemp;
cnt--;
return it;
}
void moveToStart()
{
curr = head;
}
void moveToEnd()
{
curr = tail;
}
//单向链表所有结点指向下一元素,故其prev方法较复杂
//可以通过双链表来解决该问题
void prev()
{
if (head == curr)return;
Link<E>* itemp = head;
while (itemp->next != curr)
{
itemp = itemp->next;
}
curr = itemp;
}
void next()
{
if (curr == tail)return;
curr = curr->next;
}
int length() const
{
return cnt;
}
int currPos() const
{
Link<E>* itemp = head;
int m_currpos = 0;
while (itemp != curr)
{
itemp = itemp->next;
m_currpos++;
}
return m_currpos;
}
void moveToPos(int pos)
{
assert((pos >= 0) && (pos <= cnt));
curr = head;
for (int i = 0; i < pos; i++)
{
curr = curr->next;
}
}
const E& getValue()const
{
assert(curr != tail);
return curr->next->element;
}
栈
限定仅在一端进行插入或删除操作的线性表
ADT:
template<typename>
class Stack
{
private:
void operator =(const Stack&)()
Stack(const Stack&){}
public:
Stack() {}
virtual ~Stack() {}
virtual void clear() = 0;
virtual void push(const E& it) = 0;
virtual E pop() = 0;
virtual const E& topValue()const = 0;
virtual int length()const = 0;
};
顺序实现
本质上是顺序表实现的简化
可以在一个数组中储存两个栈(双向)
初始长度固定,空间浪费
子类新增的私有成员:
private:
int maxSize;
int top;
E* listArray;
成员函数的实现:
AStack(int size = 10)
{
maxSize = size;
top = 0;
listArray = new E[size];
}
~AStack()
{
delete [] listArray;
}
void clear()
{
top = 0;
}
void push(const E& it)
{
assert(top != maxSize);
listArray[top++] = it;
}
E pop()
{
assert(top != 0);
return listArray[--top];
}
const E& topValue()const
{
assert(top != 0);
return listArray[top-1];
}
int length()const
{
return top;
}
链式实现
长度可变
每个元素需要一个链接域,结构性开销
新增的私有成员:
private:
Link<E>* top;
int size;
成员函数实现:
public:
LStack()
{
top = NULL;
size = 0;
}
~LStack()
{
clear();
}
void clear()
{
while (top != NULL)
{
Link<E>* temp = top;
top = top->next;
delete temp;
}
size = 0;
}
void push(const E& it)
{
top = new Link<E>(it, top);
size++;
}
E pop()
{
assert(top != NULL);
E it = top->element;
Link<E>* itemp = top->next;
delete top;
top = itemp;
size--;
return it;
}
const E& topValue()const
{
assert(top != NULL);
return top->element;
}
int length()const
{
return size;
}
队列
一种受限制的线性表:
队列元素只能从队尾插入(rear),队首删除(front)
ADT:
template<typename E>
class Queue
{
private:
void operator = (const Queue&){}
Queue(const Queue&){}
public:
Queue(){}
virtual ~Queue() {}
virtual void clear() = 0;
virtual void enqueue(const E&) = 0;
virtual E dequeue() = 0;
virtual const E& frontValue()const = 0;
virtual int length()const = 0;
};
顺序实现
front永远标识头部元素,rear永远标识尾部元素
取模操作:
可将数组转换成一个首尾相连的循环数组
在初始的最大长度上+1:
使数组元素不被存满,用于区分空队列与满队列
若数组元素存满,则此时rear与front的状态与空队列相同
新增的私有成员:
private:
int maxSize;
int front;
int rear;
E* listArray;
成员函数实现:
public:
Aqueue(int size = 10)
{
maxsize = size++;
rear = 0;
front = 1;
listArray = new E[maxSize];
}
~AQueue() { delete[] listArray; }
void clear() { rear = 0; front = 1; }
void enqueue(const E& it)
{
assert(((rear + 2) % maxSize) != front);
rear = (rear + 1) % maxSize;
listArray[rear] = it;
}
E dequeue()
{
assert(length() != 0);
E it = listArray[front];
front = (front + 1) % maxSize;
return it;
}
const E& frontValue() const
{
assert(length() != 0);
return listArray[front];
}
virtual int length()const
{
return (((rear + maxSize) - front + 1) % maxSize);
}
链式实现
链表的简单化
且在空间上与顺序实现的差异与栈类似,故不过多赘述
新增的私有成员:
private:
Link<E>* front;
Link<E>* rear;
int size;
成员函数实现:
public:
Lqueue(int sz = 10)
{
front = rear = new Link<E>();
size = 0;
}
~Lqueue() { clear(); delete front; }
void clear()
{
Link<E>* itemp;
while (front != NULL)
{
itemp = front;
front = front->next;
delete itemp;
}
front = rear = new Link<E>();
size = 0;
}
void enqueue(const E& it)
{
rear->next = new Link<E>(it, NULL);
rear = rear->next;
size++;
}
E dequeue()
{
assert(size != 0);
E it = front->next->element;
Link<E>* itemp = front->next;
front->next = itemp->next;
if (rear == itemp)rear = front;
delete itemp;
size--;
return it;
}
const E& frontValue()const
{
assert(size != 0);
return front->next->element;
}
virtual int length() const { return size; }