就算沒有天分,只要你願意每天花一點時間,做同樣一件事情,不知不覺間,你就會走得很遠。
什麼是線性表?
線性表是n(n>= 0)個元素的有限序列。在表中,元素之間存在這線性的邏輯關係:
(1)表中有且僅有一個開始結點;
(2)有且僅有一個終端結點;
(3)除開始結點外,表中的每一個結點均只有一個前驅結點;
(4)除終端結點外,表中的每一個結點均只有一個後繼結點;
根據他們之間的關係可以排成一個現線性序列,記作:(a1,a2,...,an)
例如:26個英文字母表(A,B,C,...,X,Y,Z)就是一個線性表
這裏的ai(1<=i<=n)屬於同一數據對象,具有相同的數據類型。線性表中的數據元素
個數n就是線性表的長度,稱作表長,n=0時爲空表。i則是數據元素ai的位序。
線性表的存儲方式
線性表有兩種存儲方式:順序存儲和鏈式存儲
(一)順序表
順序存儲是在內存中用一塊地址連續的存儲空間按順序存儲線性表的各個數據元素。
採用順序存儲結構的性表稱爲順序表,順序表中邏輯上相鄰的數據元素在物理存儲位置上也是相鄰的。
只要確定了存儲線性表的起始位置,線性表中的任意一個數據都可隨機存取:
第i個元素的地址爲:Loc(ai) = Loc(a1) + size * (i - 1) 其中size是每個元素所佔的空間大小。
C語言版的實現
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define NOTFOUND -1
#define MAXSIZE 100
#define seqNodeDataType int
//結構體定義
typedef struct seqNode{
seqNodeDataType data[MAXSIZE];
int length;
}SeqNode;
//初始化
void Init_SeqList(SeqNode * L){
L->length = 0;
}
//判空
int IsEmpty_SeqList(SeqNode L){
if (L.length == 0) {
return TRUE;
} else {
return FALSE;
}
}
//判滿
int IsFull_SeqList(SeqNode L){
if (L.length == MAXSIZE - 1) {
return TRUE;
} else {
return FALSE;
}
}
//獲取真實長度
int GetListLength_SeqList(SeqNode L){
return L.length;
}
//獲得index處的元素
seqNodeDataType GetElem_SeqList(SeqNode L, int index){
if (index < 0 || index >= L.length) {
return ERROR;
}
return L.data[index];
}
//按值查找
int IndexOf_SeqList(SeqNode L, seqNodeDataType elem){
for (int i = 0; i < L.length; ++i) {
if (L.data[i] == elem) {
return i;
}
}
return NOTFOUND;
}
//打印輸出
void Print_SeqList(SeqNode L){
printf("[");
for (int i = 0; i < L.length; ++i) {
if (i == L.length - 1) {
printf("%d", L.data[i]);
} else {
printf("%d,", L.data[i]);
}
}
printf("]\n");
}
//在index處插入元素
int InsertOfIndex_SeqList(SeqNode *L, int index, seqNodeDataType elem){
if (index < 0 || index > L->length) {
return ERROR;
}
if (IsFull_SeqList(*L)) {
return ERROR;
}
for (int i = L->length; i > index; --i) {
L->data[i] = L->data[i - 1];
}
L->data[index] = elem;
L->length++;
return OK;
}
//首部添加元素
int InsertFirst_SeqList(SeqNode *L, seqNodeDataType elem){
InsertOfIndex_SeqList(L, 0, elem);
}
//尾部添加元素
int InsertLast_SeqList(SeqNode *L, seqNodeDataType elem){
InsertOfIndex_SeqList(L, L->length, elem);
}
//刪除元素
int DeleteOfIndex_SeqList(SeqNode *L, int index, seqNodeDataType *result){
if (index < 0 || index > L->length) {
return ERROR;
}
if (IsEmpty_SeqList(*L)) {
return ERROR;
}
for (int i = index; i < L->length - 1; ++i) {
L->data[i] = L->data[i + 1];
}
L->length--;
return OK;
}
//清空數據
int Clear_SeqList(SeqNode *L){
L->length = 0;
return OK;
}
缺點在哪裏
由於順序表底層實現是一個數組,而數組的容量無法改變,因而存在的最大問題就是空間分配多大才合適呢?
過小導致數據無法繼續存放,而過大則造成空間的浪費。可以使用高級語言來優化順序表,實現動態數組擴容。
Java版實現
package cn.boom.list;
public class SeqList<T> {
private T[] data;
private int size;
//無參構造
public SeqList(){
data = (T[]) new Object[10];
size = 0;
}
//帶參構造 :參數 容量
public SeqList(int capacity) {
data = (T[]) new Object[capacity];
size = 0;
}
/**
* 獲取真實長度(數據個數)
* @return size
*/
public int getSize(){
return this.size;
}
/**
* 獲取index索引位置的元素
* @param index
* @return data[index]
* @throws IllegalArgumentException 參數不合法異常
*/
public T getIndexOf(int index) throws IllegalArgumentException{
//參數合法性校驗
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal Index !");
}
return this.data[index];
}
//修改值
public void set(int index, T elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal Index !");
}
data[index] = elem;
}
/**
* 按值查找 返回elem第一次出現的下標 未找到返回-1
* @param elem
* @return index
*/
public int locationElem(T elem){
for (int i = 0; i < this.size; i++) {
if (elem.equals(this.data[i])) {
return i;
}
}
return -1;
}
//是否存在元素
public boolean contains(T elem){
return locationElem(elem) != -1;
}
//表是否爲空
public boolean isEmpty(){
return (size == 0);
}
//表是否爲空
public boolean isFull(){
return (size == data.length);
}
//獲取容量
public int getCapacity(){
return data.length;
}
//在 index處插入一個元素
public void add(int index, T elem) {
//參數合法性校驗
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal Index ! index is " + index);
}
if (isFull()) {
updateCapacity(data.length * 2);
}
//在 index 後的數據後移
for (int i = size; i > index; i--) {
this.data[i] = this.data[i - 1];
}
this.data[index] = elem;
size++;
}
//在數組首部添加元素
public void addFirst(T elem) {
add(0, elem);
}
//在數組尾部添加元素
public void addLast( T elem){
add(this.size,elem);
}
//更新數組容量
public void updateCapacity(int newCapacity) {
T[] newArray = (T[]) new Object[newCapacity];
for (int i = 0; i < size; i++) { // copy原數組中的數據
newArray[i] = data[i];
}
this.data = newArray;
}
//刪除下標爲index的元素並返回
public T remove(int index){
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal Index ! index is " + index);
}
T elem = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
if (size < data.length / 2) { //縮容
updateCapacity(data.length / 2);
}
return elem;
}
//刪除第一個元素
public T removeFirst() {
return remove(0);
}
//刪除最後一個元素
public T removeLast() {
return remove(size - 1);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("SeqList{");
res.append(" data=[");
for (int i = 0; i < this.size; i++) {
res.append(this.data[i].toString());
if (i != size - 1) {
res.append(',');
}
}
res.append("], size=" + size + ", capacity=" + getCapacity() + " }");
return res.toString();
}
}
以上的代碼雖然實現了數組空間的動態擴容和縮容,但順序表依然是一種非動態的存儲結構。
下面介紹動態存儲結構的鏈表。
(二)單鏈表
鏈表是通過一組任意的存儲單元來存儲線性表中的數據元素。這組存儲單元可以是連續的,也可以是不連續的。
爲建立起數據元素之間的線性關係,對於每個數據元素an,除了存放數據元素自身的信息外,還必須有包含指示該元素直接後繼元素存儲位置的信息,
這兩部分信息組成個“結點”,即每個結點都至少包括兩個域,一個域存儲數據 元素信息,稱爲數據域;另一個域存儲直接後繼元素的地址,稱爲指針域。
有時爲了操作方便,在單鏈表的第一個結點之前附加一 個結點, 稱爲頭結點。頭結點的數據域可以存儲標題、表長等信息,也可以不存儲任何信息,其指針域存儲第個結點的首地址, 頭結點由頭指針指向。
單鏈表C語言版
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define NOTFOUND -1
#define linkedListDataType int
typedef struct linkedNode{
linkedListDataType data;
struct linkedNode* next;
}LNode;
//頭插法建立單鏈表
int CreateHead_LinkedList(LNode ** H){
(*H)->next = NULL;
int elem;
scanf("%d",&elem);
while (elem != -1) {
LNode* s = (LNode *) malloc(sizeof(LNode));
s->data = elem;
s->next = (*H)->next;
(*H)->next = s;
scanf("%d", &elem);
}
return OK;
}
//初始化鏈表
LNode *Init_LinkedList() {
LNode *H = (LNode *) malloc(sizeof(LNode));
if (H == NULL) {
return ERROR;
}
H->next = NULL;
return H;
}
//尾插法建立單鏈表
int CreateTail_LinkedList(LNode ** H){
(*H)->next = NULL;
LNode *p = *H;
int elem;
scanf("%d",&elem);
while (elem != -1) {
LNode* s = (LNode *) malloc(sizeof(LNode));
s->data = elem;
s->next = NULL;
p->next = s;
p = s;
scanf("%d", &elem);
}
}
//獲取鏈表長度
int GetSize_LinkedList(LNode *H){
LNode *p = H;
int size = 0;
while (p->next != NULL) {
size++;
p = p->next;
}
return size;
}
//判斷鏈表是否爲空
int IsEmpty(LNode *H){
if ( H == NULL || GetSize_LinkedList(H) == 0) {
return 1;
}
return 0;
}
//輸出打印鏈表
void Print_LinkedList(LNode *H){
LNode *p = H;
printf("head -> ");
while (p->next != NULL) {
printf("%d -> ", p->next->data);
p = p->next;
}
printf("NULL\n");
}
//獲取索引結點
LNode* GetNode_LinkedList(LNode *H, int index) {
if (index == -1) {
return H;
}
LNode *p = H;
int count = 0;
while (p->next != NULL) {
p = p->next;
if (index == count) {
return p;
}
count++;
}
return NULL;
}
//通過索引刪除結點
int DeleteNodeByIndex_LinkedList(LNode *H, int index) {
LNode *p = GetNode_LinkedList(H, index - 1);
LNode *q = p->next;
p->next = p->next->next;
free(q);
return OK;
}
//在索引處插入一個結點
int InsertNodeIndexOf_LinkedList(LNode *H, int index, linkedListDataType elem) {
if (index > GetSize_LinkedList(H)) {
return ERROR;
}
LNode *p = GetNode_LinkedList(H, index - 1);
LNode* s = (LNode *) malloc(sizeof(LNode));
if (s == NULL) {
return ERROR;
}
s->data = elem;
s->next = p->next;
p->next = s;
return OK;
}
//在鏈表尾部插入一個元素
int InsertNodeTail_LinkedList(LNode *H, linkedListDataType elem) {
InsertNodeIndexOf_LinkedList(H,GetSize_LinkedList(H),elem);
return OK;
}
//修改索引處結點的值
int UpdateNodeByIndex_LinkedList(LNode *H, int index, linkedListDataType elem){
LNode *p = GetNode_LinkedList(H, index);
p->data = elem;
return OK;
}
//刪除重複元素結點
void DeleteRepeatElem(LNode *H){
if (H == NULL) {
return;
}
LNode *p = H->next;
if (p == NULL) {
return;
}
while (p->next != NULL) {
LNode *q = p->next;
while (q != NULL) {
if (p->data == q->data) {
//刪除q結點
LNode *t = q;
q = q->next;
free(t);
p->next = q;
} else {
q = q->next;
}
}
p = p->next;
}
}
Java實現單鏈表
package cn.boom.list;
public class LinkedList<T> {
private Node head;//虛擬頭結點
private int size;//表長
private class Node {
private T data;
private Node next;
public Node() {
this.data = null;
this.next = null;
}
public Node(T data) {
this.data = data;
this.next = null;
}
public Node(T data, Node node) {
this.data = data;
this.next = node;
}
}
public LinkedList() {
this.head = new Node();
this.size = 0;
}
/**
* 獲取鏈表長度
* @return
*/
public int getSize() {
return this.size;
}
/**
* 判斷鏈表是否爲空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在鏈表的index(0-size)位置添加新的元素
* @param index
* @param elem
*/
public void add(int index, T elem) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Index Is Illegal Argument ! index = " + index);
}
Node node = this.head;
for (int i = 0; i < index ; i++) { //找到待插入下標的前一個結點
node = node.next;
}
Node e = new Node(elem);
//插入結點
e.next = node.next;
node.next = e;
this.size++;
}
/**
* 在鏈表首部位置添加新的元素
* @param elem
*/
public void addFirst(T elem) {
add(0,elem);
}
/**
* 在鏈表尾部位置添加新的元素
* @param elem
*/
public void addLast(T elem) {
add(size,elem);
}
/**
* 獲得鏈表的第index(0-size)個位置的元素
* @param index
* @return
*/
public T get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Index Is Illegal Argument ! index = " + index);
}
Node node = this.head.next;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node.data;
}
/**
* 修改鏈表的第index(0-size)個位置的元素
* @param index
* @param elem
*/
public void set(int index, T elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Index Is Illegal Argument ! index = " + index);
}
Node node = this.head.next;
for (int i = 0; i < index; i++) {
node = node.next;
}
node.data = elem;
}
/**
* 查詢表中是否包含該元素
* @param elem
* @return
*/
public boolean contains(T elem) {
Node node = this.head.next;
while (node != null) {
if (node.data.equals(elem)) {
return true;
}
node = node.next;
}
return false;
}
/**
* 刪除index(0-size)處的結點並返回數據
* @param index
* @return
*/
public T remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Index Is Illegal Argument ! index = " + index);
}
Node node = this.head;
for (int i = 0; i < index - 1; i++) { //找到要刪除節點的前一個結點
node = node.next;
}
Node s = node.next;
node.next = s.next;
s.next = null;
this.size--;
return s.data;
}
/**
* 從表中刪除元素e
* @param elem
*/
public void removeElement(T elem) {
Node preNode = this.head;
Node node = preNode.next;
while (node != null) {
if (node.data.equals(elem)) {
preNode.next = node.next;
node.next = null;
node = preNode.next;
this.size--;
continue;
}
preNode = node;
node = node.next;
}
}
@Override
public String toString() {
Node node = this.head.next;
StringBuilder res = new StringBuilder();
res.append("head -> ");
for (int i = 0; i < this.size; i++) {
res.append(node.data.toString() + " -> ");
node = node.next;
}
res.append("NULL");
return res.toString();
}
}
順序表和鏈表的優缺點
順序表優點:
(1)用數組存儲數據元素,操作方法簡單,容易實現。
(2)無須爲表示結點間的邏輯關係而增加額外的存儲開銷。
(3)存儲密度高。
(4)順序表可按元素位序隨機存取結點。
缺點:
(1)做插入、刪除操作時,須大量地移動數據元素,效率比較低。
(2)要佔用連續的存儲空間,存儲分配只能預先進行。如果估計過大,可能導致後部大量空閒置;如果預先分配過小,又會造成數據溢出。
鏈表的優缺點剛好和順序表相反。
參考文獻
[1]王曙燕.數據結構與算法:人民郵電出版社