線性表__資料結構筆記

xh_Dopamine發表於2020-10-25

線性表是最基本、最簡單、也是最常用的一種資料結構。線性表(linear list)是資料結構的一種,線性表是n個資料元素構成的有限序列,表中元素的數量為表的長度。長度為零的表為空表。

特點:

  • 集合中必存在唯一的一個“第一元素”。

  • 集合中必存在唯一的一個 “最後元素” 。

  • 除最後一個元素之外,均有唯一的後繼(後件)。

  • 除第一個元素之外,均有唯一的前驅(前件)。

邏輯結構圖:

a1————a2————a3————a4--------------an

儲存結構:
在這裡插入圖片描述

一、順序儲存

1.順序表

使用陣列實現,一組地址連續的儲存單元,陣列大小有兩種方式指定,一是靜態分配,二是動態擴充套件。

注:線性表從1開始,而陣列從0開始。

優點:

  • 有隨機訪問特性,查詢O(1)時間
  • 無需額外空間,儲存密度高

缺點:

  • 插入和刪除操作需移動大量元素
  • 表的容量難以確定

順序表的實現

用C++的類來實現順序表,由於資料型別不確定,所以採用模板機制。

const int MAXSIZE = 1e5;
template<typename T>
class SeqList {
	T data[MAXSIZE];
	int length;
public:
	SeqList();
	SeqList(T a[], int n);
	~SeqList();
	int GetLength();//求線性表的長度
	T Get(int index);//按索引查詢
	int Locate(T x);//按值查詢
	void Insert(T x, int index);//插入
	T Delete(int index);//按位置刪除
	bool Empty();//判斷是否為空
	void printSetList();//遍歷輸出
};

無參建構函式—初始化順序表

建立一個空的順序表,將length初始化為0即可。

template<typename T>
SeqList<T>::SeqList()
{
	length = 0;
}

有參建構函式—建立順序表

將資料元素傳入順序表中,資料元素個數即為順序表長度。如果順序表的儲存空間小於給定的元素個數,此時無法建立順序表。

template<typename T>
SeqList<T>::SeqList(T a[], int n)
{
	if (n > MAXSIZE)throw"非法引數!";
	for (int i = 0; i < n; i++)
		data[i] = a[i];
	length = n;
}

解構函式—銷燬順序表

順序表是靜態儲存分配,自動釋放記憶體。所以解構函式為空。

template<typename T>
SeqList<T>::~SeqList()
{
	//空
}

求線性表的長度

返回length的值即可

template<typename T>
int SeqList<T>::GetLength()
{
	return length;
}

按位查詢

順序表下表為i的元素儲存在陣列中下標為i-1的位置。時間複雜度為O(1)。

template<typename T>
T SeqList<T>::Get(int index)
{
	if (index < 0 || index >= length)
		throw"非法輸入!";
	return data[index - 1];
}

按值查詢

需要對順序表中的元素進行比較,查詢成功返回序號(下標+1),否則返回0。時間複雜的為O(n)。

template<typename T>
int SeqList<T>::Locate(T x)
{
	for (int i = 0; i < length; i++)
		if (data[i] == x)
			return i + 1;
	return 0;
}

插入操作

輸人:插入位置i,待插入的元素值x
輸出:如果插人成功,返回新的順序表,否則返回插入失敗資訊
1.如果表滿了,則輸出上溢錯誤資訊,插入失敗;
2.如果元素的插人位置不合理,則輸出位置錯誤資訊,插人失敗;
3.將最後一個元素直至第1個元素分別向後移動一個位置;
4.將元素x填人位置i處;
5.表長加1;
時間複雜度為O(n)

template<typename T>
void SeqList<T>::Insert(T x, int index)
{
	if (length = MAXSIZE)throw("上溢!");
	if (index < 0 || index >= length)throw("非法輸入!");
	for (int i = length; i >= index; i--)
		data[i] = data[i - 1];
	data[index - 1] = x;
	length++;
}

刪除操作

從刪除位置開始,將資料元素前移一位即可,注意下標的表示。時間複雜度為O(n)

template<typename T>
T SeqList<T>::Delete(int index)`在這裡插入程式碼片`
{
	if (length = 0)throw("下溢!");
	if (index < 0 || index >= length)throw("刪除位置錯誤!");
	T x = data[index - 1];
	for (int i = index; i < length; i++)
		data[i - 1] = data[i];
	length--;
	return x;
}

判斷順序表是否為空

判斷length的值即可

template<typename T>
bool SeqList<T>::Empty()
{
	if (length > 0)
		return true;
	return false;
}

遍歷輸出

template<typename T>
void SeqList<T>::printSetList()
{
	for (int i = 0; i < length; i++)
	{
		cout << data[i] << " ";
		if (i % 5 == 0)
			cout << endl;
	}
}

二、鏈式儲存

1.單連結串列

單連結串列是用一組任意的儲存單元存放線性表的元素,這組儲存單元可以連續也可以不連續,甚至可以零散分佈在記憶體中的任意位置。

每個儲存單元在儲存資料元素的同時,還必須儲存其後繼元素所在的地址資訊,這個地址資訊稱為指標。這兩部分組成了資料元素的儲存映象,稱為結點

每個結點的儲存地址存放在其前驅結點的next域中,而第一個元素無前驅,所以設頭指標指向第一個元素所在結點(稱為開始結點),整個單連結串列的存取必須從頭指標開始進行,因而頭指標具有標識一個單連結串列的作用。最後一個元素無後繼,故最後一個元素所在結點(稱為終端結點)的指標域為空,這個空指標稱為尾標誌。

通常在單連結串列的開始結點之前附設一個型別相同的結點,稱為頭結點。也有不帶頭結點的單連結串列,不過其操作更為麻煩,所以不經常使用,我們下面會簡單介紹。如不特殊說明,使用的連結串列都是預設帶頭結點的。

單連結串列的實現

結點的定義

data為資料區,存放資料
next為指標域,存放下一個結點的地址

template<typename T>
struct Node 
{
	T data;
	Node<T>* next; 
};

宣告:

template<typename T>
class LinkList {//有首結點
	Node<T>* first;//頭指標
public:
	LinkList();
	LinkList(T a[], int n);//頭插法
	LinkList(int n,T a[])//尾插法
	T Get(int index);//按位查詢
	int Locate(T x);//按值查詢
	void Insert(int index, T x);//插入
	T Delete(int index);//刪除
	void Delete_Data(T x);//按值刪除
	void PrintList();//遍歷輸出
	int Length();//連結串列長度
	int Empty();//判斷連結串列是否為空
	~LinkList();//解構函式
};

無參構造

帶頭結點

建立一個帶首節點的空連結串列
給first指標申請一塊記憶體,將其指標域賦空值(代表無後繼結點),即空連結串列

template<typename T>
LinkList<T>::LinkList()
{
	first = new Node<T>;
	first->next = NULL;
}

不帶頭結點

直接把頭指標賦空值

template<typename T>
LinkList<T>::LinkList()
{
	first = NULL;
}

有參構造(頭插法)

帶頭結點

頭插法就是把結點插入到首節點之後
先建立結點s儲存資料
將s的指標域指向原來的第一個結點,即first->next
現在s成為了第一個節點
將first->next指向s

template<typename T>
LinkList<T>::LinkList(T a[], int n)
{
	first = new Node<T>;
	first->next = NULL;
	Node<T>* s = NULL;
	for (int i=0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		s->next = first->next;
		first->next = s;
	}
}

不帶頭節點

把插入的節點作為首結點

template<typename T>
LinkList<T>::LinkList(T a[], int n)
{
	first = NULL;
	Node<T>* s = new Node<T>;

	for (int i = 0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		s->next = first;
		first = s;
	}
}

有參構造(尾插法)

帶頭結點

尾插法就是在連結串列的尾部插入節點
為了操作方便,這裡需要增加一個尾指標last
定義一個結點s儲存資訊
將尾指標的指標域指向新插入的s
現在結點s才是真正的隊尾
然後再將尾指標指向s
所有元素都插入後,將尾指標的next賦空值

template<typename T>
LinkList<T>::LinkList(int n, T a[])
{
	first = new Node<T>;
	first->next = NULL;
	Node<T>* s = NULL;
	Node<T>* last = first;
	for (int i = 0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		last->next = s;
		last = s;
	}
	last->next = NULL;
}

不帶頭節點

操作相對來說較複雜一些,第一個節點和後續結點的操作不同
需要先插入第一個結點,再迴圈插入後續結點

template<typename T>
LinkList<T>::LinkList(T a[],int n)
{
	Node<T>* s = new Node<T>;
	s->data = a[0];
	first = s;
	Node<T>* last = first;
	for (int i = 1; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		last->next=s;
		last = s;
	}
	last->next = NULL;
}

按位置查詢值

這裡需要一個工作指標p和一個計數器count來計數
當count==index-1時,此時的p就是要查詢的結點
輸出p->data

template<typename T>
T LinkList<T>::Get(int index)
{
	int count = 0;//計數器
	Node<T>* p = first->next;//工作指標

	while (p && count < index-1)
	{
		count++;
		p = p->next;//工作指標後移
	}
	if (!p)throw"查詢失敗";
	return p->data;
}

按值查詢位置

還是需要計數器count來計數,當p->next==x時,輸出此時的count

template<typename T>
int LinkList<T>::Locate(T x)
{
	Node<T>* p = first->next;
	int count = 1;
	while (p)
	{
		if (p->data == x)return count;//查詢成功
		count++;
		p = p->next;
	}
	if (!p)throw"查詢失敗";
}

插入操作

有首節點

使用結點s來記錄資料
插入需要查詢到第i-1個結點,然後再修改指標變數的值
將s->next指向下一個結點,即第i-1個結點儲存的地址(next)
然後將第i-1個結點的next指向s

template<typename T>
void LinkList<T>::Insert(int index, T x)
{
	Node<T>* p = first;
	int count = 0;

	while (p && count < index - 1)
	{
		p = p->next;
		count++;
	}
	if (p)
	{
		Node<T>* s = new Node<T>;
		s->data = x;
		s->next = p->next;
		p->next = s;
	}
	else
		throw"插入失敗";
}

無首結點

需要判斷插入的位置,插入在第一個元素的操作不同

template<typename T>
void LinkList<T>::Insert(int index, T x)
{
	Node* p=NULL;
	Node* s = NULL;
	int count;
	if (index <= 0) throw"位置非法";
	if (index == 1)
	{
		s = new Node<T>;
		s->data = x;
		s->next = first;
		first = s;
		return;
	}

	p = first;
	count = 1;
	while (p && count < i - 1)
	{
		p = p->next;
		count++;
	}
	if (!p)throw"位置非法";
	else
	{
		s = new Node<T>;
		s->data = x;
		s->next = p->next;
		p->next = s;
	}
}

按位置刪除

刪除操作也是需要找到第i-1個結點
然後將第i-1個結點的next指向第i+1個結點,即第i個結點的next
然後釋放第i個結點的記憶體

template<typename T>
T LinkList<T>::Delete(int index)
{
	T x;
	Node<T>* p = first;
	Node<T>* q = NULL;
	int count = 0;
	while (p && count < index - 1)
	{
		p = p->next;
		count++;
	}
	if (!p || !p->next)"刪除失敗";
	else
	{
		q = p->next;
		x = q->data;
		p->next = q->next;
		delete q;
		return x;
	}
}

按值刪除

先查詢到和資料相等的值所在的結點p
再修改其前驅結點p的的指標域

void LinkList::Delete_Data(int x)//按值刪除
{
    Node* p = first;//前驅
    Node* q = NULL;//後繼
    Node* temp = NULL;
    bool flag = false;

    while (p->next)
    {
        if (p->next->data == x)
        {
            q = p->next;
            p->next = q->next;
            delete q;
            flag = true;
        }
       p = p->next;
    }
    if (!flag)throw"刪除失敗";
}

求連結串列的長度

遍歷計數即可

template<typename T>
int LinkList<T>::Length()
{
	Node<T>* p = first->next;
	int count = 1;
	while (p)
	{
		count++;
		p = p->next;
	}
	return count;
}

判斷連結串列是否為空

判斷first->next是否為空

template<typename T>
int LinkList<T>::Empty()
{
	if (first->next == NULL)
		return 1;
	return 0;
}

輸出連結串列

遍歷輸出即可

template<typename T>
void LinkList<T>::PrintList()
{
	Node<T>* p = first->next;
	while (p)
	{
		cout << p->data <<" ";
		p = p -> next;
	}
	cout << endl;
}

解構函式

釋放記憶體

template<typename T>
LinkList<T>::~LinkList()
{
	Node<T>* p = NULL;
	while (first)
	{
		p = first->next;
		delete first;
		first = p;
	}
}

2.迴圈連結串列

將單連結串列的頭尾結點連線起來,就是一個迴圈連結串列。沒有增加額外的開銷,為連結串列的操作增添了不少便利。
特點:從連結串列的任意一點出發,都能訪問到表中其他節點。

整體的方法和單連結串列的差異不大,只要稍作修改即可。

構造空表

首尾相連

template<typename T>
CycleLinkList<T>::CycleLinkList()
{
	first = new Node<T>;
	first->next = first;
}

有參構造(頭插法)

帶頭結點

和單連結串列構造的唯一差距就是將其中一條語句修改
first->next=first

template<typename T>
LinkList<T>::LinkList(T a[], int n)
{
	first = new Node<T>;
	first->next = first;//修改
	Node<T>* s = NULL;
	for (int i=0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		s->next = first->next;
		first->next = s;
	}
}

有參構造(尾插法)

帶頭結點

last->next=first

template<typename T>
LinkList<T>::LinkList(int n, T a[])
{
	first = new Node<T>;
	first->next = NULL;
	Node<T>* s = NULL;
	Node<T>* last = first;
	for (int i = 0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		last->next = s;
		last = s;
	}
	last->next = first;
}

將單連結串列變為迴圈連結串列

Node*p=first;
while(p->next)
{
	p=p->next;
}
p->next=first;

其他的操作與單連結串列的操作基本相同們這裡不再贅述。

3.雙連結串列

在雙連結串列中,每個結點不僅記錄了資料元素,還儲存了前驅結點和後繼結點的地址資訊。能方便的找到前驅節點。

結點構成

template<typename T>
struct Node
{
	T data;
	Node<T>* pre;
	Node<T>* next;
};

宣告

template<typename T>
class DoubleLink {

	Node<T>* first;
public:
	DoubleLink();
	DoubleLink(T a[], int n);
	T Get(int index);
	int Get_data(T x);
	T Delete(int index);
	T Delete_data(T x);
	void Insert(int index, T x);
	int Length();
	void Print();
	~DoubleLink();
};

無參構造

template<typename T>
DoubleLink<T>::DoubleLink()
{
	first = new Node<T>;
	first->pre = NULL;
	first->next = NULL;
}

頭插法構造

先正向修改指標,再反向修改指標,同時注意判斷p->next是否存在

template<typename T>
DoubleLink<T>::DoubleLink(T a[],int n)
{
	first = new Node<T>;
	first->pre = NULL;
	first->next = NULL;
	Node<T>* s = NULL;
	for (int i = 0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		s->next = first->next;
		first->next = s;
		s->pre = first;
		if(s->next)
			s->next->pre = s;
	}
}

尾插法構造

利用尾指標進行操作

template<typename T>
DoubleLink<T>::DoubleLink(T a[], int n)
{
	first = new Node<T>;
	first->pre = NULL;
	first->next = NULL;
	Node<T>* s = NULL;
	Node<T>* last = first;

	for (int i = 0; i < n; i++)
	{
		s = new Node<T>;
		s->data = a[i];
		last->next = s;
		s->pre = last;
		last = s;
	}
	last->next = NULL;
}

按位置查詢值

和單連結串列操作相同

template<typename T>
T LinkList<T>::Get(int index)
{
	int count = 0;//計數器
	Node<T>* p = first->next;//工作指標

	while (p && count < index-1)
	{
		count++;
		p = p->next;//工作指標後移
	}
	if (!p)throw"查詢失敗";
	return p->data;
}

按值查詢位置
和單連結串列操作相同

template<typename T>
int LinkList<T>::Locate(T x)
{
	Node<T>* p = first->next;
	int count = 1;
	while (p)
	{
		if (p->data == x)return count;//查詢成功
		count++;
		p = p->next;
	}
	if (!p)throw"查詢失敗";
}

插入操作

和單連結串列基本相同,就只有修改指標操作不同

template<typename T>
void DoubleLink<T>::Insert(int index, T x)
{
	if (index <= 0)throw"位置非法";
	Node<T>* p = first;
	int count = 0;
	while (p&&count<index-1)
	{
		p = p->next;
		count++;
	}
	if (p)
	{
		Node<T>* s = new Node<T>;
		s->data = x;
		s->next = p->next;
		p->next = s;
		s->pre = p;
		if (s->next)
			s->next->pre = s;
	}
	else
		throw"位置非法"
	
}

按位置刪除
和單連結串列稍微有些不同
不需要找前驅節點,直接找到第index個結點就可以

template<typename T>
T DoubleLink<T>::Delete(int index)
{
	T x;
	Node<T>* p = first->next;
	int count = 0;
	while (p && count < index - 1)
	{
		p = p->next;
		count++;
	}
	if (!p || !p->next)"刪除失敗";
	else
	{
		p->pre->next = p->next;
		if(p->next)
			p->next->pre = p->pre;
		delete p;
		return x;
	}
}

按值刪除

思路和單連結串列基本上相同,知識細微部分不同
需要注意判斷p->next是否為空

void List::Delete(int x)
{
    Node* p=NULL, * q=NULL;
    p = first->next;
    while (p)
    {
        if (p->data == x)
        {
            q = p;
            p->pre->next = p->next;
            if (p->next != NULL)
                p->next->pre = p->pre;
            delete q;
        }
        p = p->next;
    }
}

遍歷輸出

和單連結串列操作相同

template<typename T>
void DoubleLink<T>::Print()
{
	Node<T>* p = first->next;
	while(p)
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl;
	return;
}

求連結串列的長度

遍歷計數,和單連結串列相同

template<typename T>
int LinkList<T>::Length()
{
	Node<T>* p = first->next;
	int count = 1;
	while (p)
	{
		count++;
		p = p->next;
	}
	return count;
}

解構函式

和單連結串列相同

template<typename T>
DoubleLink<T>::~DoubleLink()
{
	Node<T>* p = NULL;
	while (first)
	{
		p = first->next;
		delete first;
		first = p;
	}
}

總的來說,雙連結串列的操作的實現思路和單連結串列相同,操作上大同小異。

三、順序表和連結串列的比較

時間效能:

若線性表的操作主要是查詢,很少進行刪除、插入時,可以選擇順序表來儲存。
若進行頻繁的插入和刪除操作,適合選擇連結串列來儲存。

空間效能:

當線性表的長度變化不大,且事先容易確定其大小時,適合採用順序表來儲存,否則使用連結串列。

如果你覺得看完這篇文章有收穫,就動動小手點個贊吧!

相關文章