数据结构与算法 Chapter 2 线性表
2.1 线性表定义
线性表是由长度为n的一组节点组成的有限序列,其中除了首末结点之外,每个结点都有直接的前驱结点和后继结点。
2.2 线性表的顺序存储结构
顺序存储结构使用一组连续的存储单元来存储线性表。 其特点有:线性表的逻辑顺序与物理顺序一致、数据元素之间的关系采用物理位置的相邻来表示。其可以随机存取,常用一维数组表示:
#include <iostream>
using namespace std;
template<class Elem>
class Alist
{
private:
int maxSize;
int listSize;
int curr;
Elem * listArray;
public:
Alist(int size = 100)
{
maxSize = size;
listSize = 0;
curr = 0;
listArray = new Elem[maxSize];
}
~Alist(){ delete [] listArray;}
void clear() {listSize = curr = 0;}
void Prev() {if (curr>0) curr--;}
void Next() {if (curr < listSize-1) curr++;}
bool setPos(int pos)
{
if (pos < 0 || pos >= listSize)
return false;
curr = pos;
}
bool insert(const Elem it)
{
if (listSize == maxSize) return false;
for (int i = listSize; i > curr; i--)
listArray[i] = listArray[i-1];
listArray[curr] = it;
listSize++; // update length of the list
return true;
}
bool append(const Elem it)
{
if (listSize == maxSize) return false;
listArray[listSize++] = it;
return true;
}
bool remove(Elem & it)
{
if (!listSize) return false;
if (curr < 0 || curr >= listSize) return false;
it = listArray[curr];
for (int i = curr; i < listSize-1; i++)
listArray[i] = listArray[i+1];
listSize--;
curr--; // in case you removed the last one
return true;
}
void print()
{
for (int i = 0; i < listSize; i++)
cout << listArray[i] << " ";
cout << endl;
}
};
int main()
{
int temp;
Alist<int> mylist;
mylist.append(2);
mylist.append(10);
mylist.append(8);
mylist.append(5);
mylist.append(7);
mylist.print();
mylist.setPos(3);
mylist.insert(100);
mylist.Next();
mylist.print();
mylist.remove(temp);
mylist.remove(temp);
mylist.remove(temp);
mylist.print();
}
- 在线性表中随机访问任意位置上的元素,时间复杂度为O(1),因此顺序表示可以随机存取的。
- 在顺序存储的线性表中插入或删除一个节点,时间复杂度为O(n),而且需要移动大量元素。
- 顺序表空间扩展的代价高,往往需要重新分配。
2.3 线性表的链式存储结构
单链表
单链表的每个数据元素用一个结点来存储,每个结点包含一个数据域和一个指针域。头结点是链表最开头的结点,没有存储任何数据元素,通过指针head
找到,其他结点的地址通过前驱的next
指针找到。最后一个结点没有后继,因此其指针域内容为NULL
。下面是单链表的定义、操作实现与一个例子:
#include <iostream>
using namespace std;
template<class Elem>
class Link
{
public:
Elem element;
Link * next;
Link(const Elem & item, Link * nextval = NULL) {element = item; next = nextval;}
Link(Link * nextval = NULL) {next = nextval;}
};
template<class Elem>
class LList
{
private:
Link<Elem> * head;
Link<Elem> * tail;
Link<Elem> * curr;
void init() {curr = tail = head = new Link<Elem>;}
void removeall()
{
while (head != NULL)
{
curr = head;
head = head->next;
delete curr;
}
}
public:
LList() {init();}
~LList() {removeall();}
bool getValue(Elem & e)
{
if (curr == NULL) return false;
e = curr->element;
return true;
}
Link<Elem> * Locate(Elem e)
{
Link<Elem> * p = head;
while (p != NULL)
{
p = p->next;
if (p->element == e)
return p;
}
return NULL;
}
bool isEmpty() {return head->next == NULL;}
void Prev()
{
if (curr == head) return;
Link<Elem> * p = head;
while (p->next != curr)
p = p->next;
curr = p;
}
void Next()
{
if (curr->next == NULL)
return;
curr = curr->next;
}
bool setPos(int pos)
{
Link<Elem> * p = head;
for (int i = 0; i < pos; i++)
{
if (p->next == NULL)
return false;
p = p->next;
}
curr = p;
return true;
}
bool Insert(Elem x)
{
Link<Elem> * s = new Link<Elem>;
s->element = x;
s->next = curr->next;
curr->next = s;
return true;
}
bool remove(Elem &e)
{
if (curr->next == NULL) return false;
Link<Elem> * q = curr->next;
e = q->element;
curr->next = q->next;
delete q;
return true;
}
void clear() {removeall(); init();}
void Print()
{
Link<Elem> * p = head;
while (p->next != NULL)
{
p = p->next;
cout << p->element << " ";
}
cout << endl;
}
};
int main()
{
int a;
LList<int> mylist;
mylist.Insert(2);
mylist.Insert(3);
mylist.Insert(8);
mylist.Print();
mylist.Next();
mylist.Next();
mylist.Insert(10);
mylist.Prev();
mylist.remove(a);
mylist.Print();
return 0;
}
在单链表上,删除和插入一个结点的时间复杂度为O(1),但随机访问任意位置上的结点时间复杂度为O(n)。
顺序表与单链表比较
- 空间上:顺序表所有空间都用于存储数据,而单链表一部分空间用于存储指针域。
- 访问上:顺序表可以O(1)时间访问任意元素,而单链表随机访问的时间复杂度为O(n)。
- 插入删除上:顺序表的平均时间复杂度为O(n),而单链表的时间复杂度为O(1)。
由于插入删除是更经常的操作,因此单链表在很多方面都优于顺序表。
双向链表
一个双向链表中的结点包含两个指针域和一个数据域,其中两个指针域分别为前驱与后继,改进了单链表找前驱结点时间复杂度为O(n)的缺点。一个带头结点的双向链表的定义、操作实现与一个例子如下:
#include <iostream>
using namespace std;
template<class Elem>
class DLink
{
public:
Elem element;
DLink * prev;
DLink * next;
DLink(const Elem & it, DLink * p = NULL, DLink * n = NULL)
{
element = it;
prev = p;
next = n;
}
DLink(DLink *p = NULL, DLink *n = NULL) {prev = p; next = n;}
};
template<class Elem>
class DList
{
private:
DLink<Elem> * head, * tail, * curr;
public:
DList()
{
head = tail = curr = new DLink<Elem>;
}
~DList()
{
while (head != NULL)
{
curr = head;
head = head->next;
delete curr;
}
}
void Prev() {curr = curr->prev;}
void Next() {curr = curr->next;}
bool setPos(int pos)
{
DLink<Elem> * p = head;
for (int i = 0; i < p; i++)
{
if (p->next == NULL) return false;
p = p->next;
}
curr = p;
}
bool getValue(Elem & x)
{
if (head->next == NULL) return false;
x = curr->element;
return true;
}
bool Insert(const Elem x) // insert an element after curr
{
if (curr->next != NULL)
{
DLink<Elem> * newptr, * nextptr;
nextptr = curr->next;
newptr = new DLink<Elem>(x, curr, nextptr);
curr->next = newptr;
nextptr->prev = newptr;
return true;
}
else // when curr->next == NULL
{
DLink<Elem> * newptr = new DLink<Elem>(x, curr, NULL);
curr->next = newptr;
return true;
}
}
bool remove(Elem & x) // remove the element of curr
{
if (head->next == NULL || curr == NULL) return false;
DLink<Elem> * temp = curr;
x = temp->element;
curr->prev->next = curr->next;
curr->next->prev = curr->prev;
curr = curr->next; // reset curr to a valid node
delete temp;
return true;
}
void Print()
{
DLink<Elem> * p = head;
while (p->next != NULL)
{
p = p->next;
cout << p->element << " ";
}
cout << endl;
}
};
int main()
{
int a;
DList<int> mylist;
mylist.Insert(2);
mylist.Insert(3);
mylist.Insert(8);
mylist.Print();
mylist.Next();
mylist.Next();
mylist.Next();
//mylist.getValue(a);
//cout << a << endl;
mylist.Insert(10);
mylist.Prev();
mylist.remove(a);
mylist.Print();
return 0;
}
循环链表
循环单链表的特点在于,单链表的尾结点rear->next
为空,但循环单链表的尾结点rear->next==head
,这使得插入删除时不用考虑头尾节点的特殊性。循环双链表类似地有rear->next==head
且head->prev==rear
。下面是一个循环单链表的定义与操作实现,并用其解决Josephus问题:
#include <iostream>
using namespace std;
template<class Elem>
class CLinkNode
{
public:
Elem element;
CLinkNode<Elem> * next;
CLinkNode(CLinkNode<Elem> * nexts = NULL) : next(nexts) {};
CLinkNode(Elem d, CLinkNode<Elem> * nexts = NULL) : element(d), next(nexts) {}
};
template<class Elem>
class CList
{
private:
CLinkNode<Elem> * head, * curr;
public:
CList() {head = NULL; curr = NULL;}
~CList()
{
while (head != NULL)
{
curr = head;
head = head->next;
delete curr;
}
}
bool Insert(const Elem x)
{
if (head == NULL)
{
head = new CLinkNode<Elem>(x, head);
head->next = head;
curr = head;
curr = curr->next;
return true;
}
else
{
CLinkNode<Elem> * newptr = new CLinkNode<Elem>(x, curr->next);
newptr->next = curr->next;
curr->next = newptr;
curr = curr->next;
return true;
}
}
void Prev()
{
CLinkNode<Elem> * p = curr;
while (p->next != curr)
p = p->next;
curr = p;
}
bool remove(Elem & x)
{
if (curr == NULL) return false;
x = curr->element;
CLinkNode<Elem> * temp = curr;
Prev();
curr->next = temp->next;
delete temp;
return true;
}
CLinkNode<Elem> * getHead() {return head;}
void Print()
{
CLinkNode<Elem> * p = head;
while (p != head)
{
p = p->next;
cout << p->element << " ";
}
cout << endl;
}
};
int main()
{
int n, m;
cin >> n >> m;
CList<int> clist;
for (int i = 1; i <= n; i++)
clist.Insert(i);
clist.Print();
CLinkNode<int> * p = clist.getHead(), * pre = NULL;
for (int i = 0; i < n; i++)
{
for (int j = 1; j < m; j++)
{
pre = p;
p = p->next;
}
cout << "OUT: " << p->element << endl;
m = p->element;
pre->next = p->next;
delete p;
p = pre->next;
}
return 0;
}
2.4 线性表应用
一元多项式表示
可以利用单链表表示一元多项式,其中每个结点有两个数据与与一个指针域,两个数据域分别存储多项式当中一项的系数与次数,不同项之间的次数按从头到尾从低到高排列。在此基础上,可以定义多项式链表的加法与减法运算:
Poly sum(Poly a, Poly b)
{
Poly c;
Node * p = a.getHead()->next;
Node * q = b.getHead()->next;
Node * r = c.getHead();
while (p && q)
{
if (p->exp == q->exp)
{
Node * s = new Node(p->coef + q->coef, p->exp, NULL);
r->next = s;
r = s;
p = p->next;
q = q->next;
}
else if(p->exp > q->exp)
{
Node * s = new Node(q->coef, q->exp, NULL);
r->next = s;
r = s;
q = q->next;
}
else if(p->exp < q->exp)
{
Node * s = new Node(p->coef, p->exp, NULL);
r->next = s;
r = s;
p = p->next;
}
}
if (p) r->next = p;
else r->next = q;
return c;
}
商品链更新
与一元多项式的表示相似,也可以由单链表存储并定义加减法以追踪货物情况。
2.5 本章练习
Ex13. 设线性表L=(a1,a2,...,an)
以带头结点的单链表作存储结构。请编写函数使得当n
为奇数时让L成为L=(a2,a4,...,an-1,a1,...,an)
。
#include <iostream>
using namespace std;
template<class Elem>
class Link
{
public:
Elem element;
Link * next;
Link(const Elem & item, Link * nextval = NULL) {element = item; next = nextval;}
Link(Link * nextval = NULL) {next = nextval;}
};
template<class Elem>
class LList
{
private:
Link<Elem> * head;
Link<Elem> * tail;
Link<Elem> * curr;
void init() {curr = tail = head = new Link<Elem>;}
void removeall()
{
while (head != NULL)
{
curr = head;
head = head->next;
delete curr;
}
}
public:
LList() {init();}
~LList() {removeall();}
bool getValue(Elem & e)
{
if (curr == NULL) return false;
e = curr->element;
return true;
}
Link<Elem> * Locate(Elem e)
{
Link<Elem> * p = head;
while (p != NULL)
{
p = p->next;
if (p->element == e)
return p;
}
return NULL;
}
bool isEmpty() {return head->next == NULL;}
void Prev()
{
if (curr == head) return;
Link<Elem> * p = head;
while (p->next != curr)
p = p->next;
curr = p;
}
void Next()
{
if (curr->next == NULL)
return;
curr = curr->next;
}
bool setPos(int pos)
{
Link<Elem> * p = head;
for (int i = 0; i < pos; i++)
{
if (p->next == NULL)
return false;
p = p->next;
}
curr = p;
return true;
}
bool Insert(Elem x)
{
Link<Elem> * s = new Link<Elem>;
s->element = x;
s->next = curr->next;
curr->next = s;
curr = curr->next;
return true;
}
bool remove(Elem &e)
{
if (curr->next == NULL) return false;
Link<Elem> * q = curr->next;
e = q->element;
curr->next = q->next;
delete q;
return true;
}
void clear() {removeall(); init();}
void Print()
{
Link<Elem> * p = head;
while (p->next != NULL)
{
p = p->next;
cout << p->element << " ";
}
cout << endl;
}
Link<Elem> * getHead() {return head;}
};
template<class Elem>
void Shuffle(LList<Elem> & l)
{
Link<Elem> * l2, *p, *q;
p = l.getHead();
l2 = new Link<Elem>; // head node
q = l2;
int count = 1;
while (p->next)
{
if (count % 2 == 1)
{
q->next = p->next;
q = q->next;
p->next = p->next->next;
count++;
}
else
{
p = p->next;
count++;
}
}
p->next = l2->next;
}
int main()
{
LList<int> l;
for (int i = 1; i < 10; i++)
l.Insert(i);
l.Print();
Shuffle(l);
l.Print();
return 0;
}
Ex20. 写一算法将单链表中所有重复的结点删除,使所得结果表中各结点值均不相同。
#include <iostream>
using namespace std;
template<class Elem>
class Link
{
public:
Elem element;
Link * next;
Link(const Elem & item, Link * nextval = NULL) {element = item; next = nextval;}
Link(Link * nextval = NULL) {next = nextval;}
};
template<class Elem>
class LList
{
private:
Link<Elem> * head;
Link<Elem> * tail;
Link<Elem> * curr;
void init() {curr = tail = head = new Link<Elem>;}
void removeall()
{
while (head != NULL)
{
curr = head;
head = head->next;
delete curr;
}
}
public:
LList() {init();}
~LList() {removeall();}
bool getValue(Elem & e)
{
if (curr == NULL) return false;
e = curr->element;
return true;
}
Link<Elem> * Locate(Elem e)
{
Link<Elem> * p = head;
while (p != NULL)
{
p = p->next;
if (p->element == e)
return p;
}
return NULL;
}
bool isEmpty() {return head->next == NULL;}
void Prev()
{
if (curr == head) return;
Link<Elem> * p = head;
while (p->next != curr)
p = p->next;
curr = p;
}
void Next()
{
if (curr->next == NULL)
return;
curr = curr->next;
}
bool setPos(int pos)
{
Link<Elem> * p = head;
for (int i = 0; i < pos; i++)
{
if (p->next == NULL)
return false;
p = p->next;
}
curr = p;
return true;
}
bool Insert(Elem x)
{
Link<Elem> * s = new Link<Elem>;
s->element = x;
s->next = curr->next;
curr->next = s;
return true;
}
bool remove(Elem &e)
{
if (curr->next == NULL) return false;
Link<Elem> * q = curr->next;
e = q->element;
curr->next = q->next;
delete q;
return true;
}
void clear() {removeall(); init();}
void Print()
{
Link<Elem> * p = head;
while (p->next != NULL)
{
p = p->next;
cout << p->element << " ";
}
cout << endl;
}
Link<Elem> * getHead() {return head;}
};
template<class Elem>
void noRedun(LList<Elem> & l)
{
Link<Elem> * p, * q, * pre;
p = l.getHead();
p = p->next;
q = p->next;
pre = p;
while (p->next)
{
q = p->next;
pre = p;
while (q)
{
if (p->element == q->element)
{
Link<Elem> * temp = q;
pre->next = q->next;
delete q;
q = pre->next;
}
else
{
pre = q;
q = q->next;
}
}
p = p->next;
}
}
int main()
{
LList<int> l;
for (int i = 1; i < 10; i++)
l.Insert(rand() % 10);
l.Print();
noRedun(l);
l.Print();
return 0;
}