簡介
- 線性表是最常用且最簡單的一種數據結構,簡而言之就是n個數據元素的有限序列。
- 線性表的順序表示和實現:
線性表的順序指用一組地址連續的存儲單元一次存儲線性表的數據元素。以元素在計算機內的“物理位置相鄰”來表示線性表中數據元素之間的邏輯關係。每個數據元素的存儲位置和線性表的起始位置相差一個和數據元素在線性表中的位序成正比的常數。只要我確定了存儲線性表的起始位置,線性表中任一數據元素都可以隨機存取,所以線性表的順序存儲結構是一種隨機存取的存儲結構(存取的時候可以直接獲得線性表中的某一數據,像數組,可以通過下標直接獲取數組中的任一元素,而不需要像鏈表那樣需要通過指針從頭遍歷查找,但是線性表的順序存儲缺點是插入元素或者刪除元素都有進行大量的移動,而鏈表就不用,只需要修改指針)。
線性表的創建很簡單:
//初始化線性表
int IinitListSpace(LIN* V){
V->elem = (int*)malloc(MAX_FORM_SIZE*sizeof(int));
if(!V->elem)exit(OVERFLOW);
V->length = 0;
V->sizes = MAX_FORM_SIZE;
return 1;
}
//初始化線性表數據
int InitListData(LIN* V){
int i =0;
for(i =0 ; i<10 ; i++) {
V->elem[i] = i;
V->length++;
if(V->length >= MAX_FORM_SIZE)break;
}
return 1;
}
//向線性表任意位置插入數據
int InsertListData(LIN* V){
int data;
int space;
int i;
printf("\n請輸入需要插入的位置和整數\n");
scanf("%d",&space);
scanf("%d",&data);
if(space<1||space > V->length+1)return 0;
//如果空間已經佔滿,這增加空間
if(V->length >= V->sizes){
//重新給線性表分配內存,在原來的基礎上增加內存空間
V->elem = (int*)realloc(V->elem,(MAX_FORM_SIZE+MAX_FORM_LENTH)*sizeof(int));
if(!V->elem)exit(OVERFLOW);
V->sizes = MAX_FORM_SIZE+MAX_FORM_LENTH;
}
printf("插入之前的線性表數據\n");
for(i =0 ; i < V->length ; i++){
printf("%d ",V->elem[i]);
}
//插入數據,移動數據
for(i = V->length ; i >= space -1 ; i-- ) V->elem[i+1] = V->elem[i];
V->elem[space-1] = data;
V->length +=1;
printf("\n插入之後的線性表數據\n");
for(i =0 ; i < V->length ; i++){
printf("%d ",V->elem[i]);
}
return 1;
}
線性表的鏈式表示和實現:存儲結構的特點是用一組任意的存儲單元存儲線性表的數據元素(這組存儲單元可以是連續的,也可以不是連續的)。用線性鏈表表示線性表時,數據元素之間的邏輯關係是由結點中的指針指示的,也就是說,指針位數據元素之間的邏輯映射,則邏輯相鄰的兩個元素其存儲的物理位置不要求緊鄰,這種存儲結構爲非順序映像或鏈式映像。
線性鏈表的創建:
//線性鏈表相關操作
void* InitLinkList(LinkList V){
int i;
Node* P;
V = (LinkList)malloc(sizeof(Node));
if(!V)exit(OVERFLOW);
V->next = NULL;//建立一個帶頭節點的單鏈表
V->data = 0;
for(i = 0 ; i< 10 ;i++){
P = (LinkList)malloc(sizeof(Node));//生成節點
if(!P)exit(OVERFLOW);
//scanf("%d",&(P->data));
P->data = i;
P->next = V->next;
V->next = P;
V->data++;
}
return V;
}
這裏講一下for循環裏面怎麼創建一個鏈表的:函數傳進一個V鏈表,其指向下一個結點爲NULL,這裏我把鏈表的第一結點拿來做頭結點,當然可以重新定義一個。在for循環裏面,
當執行第一次for循環後,鏈表呈現這個樣子:執行P->next = V->next;後,P的下一個結點指向V的下一個結點,而V->next = NULL,所以這時P的下一個結點不指向任何對象(或者對象爲空),也就是P->next = NULL;執行V->next = P; 後,V的下一個結點指向P,也就是V的下一個結點指向,P的下一個結點指向NULL,這裏不要因爲P->next = V->next;而以爲P的下一個結點指回本身了,指向的是NULL,所以就是V(next)——> P(next)——>NULL;
當執行第二次for循環後,鏈表程序的樣子:執行P->next = V->next;新生成的P結點的下一個節點指向V的下一個結點,但是執行完第一次for循環後,V的下一個結點(V->next)指向了P,所以新的P節點的下一個結點指向了上一個P節點,執行V->next = P後,相當於斷開了第一次for循環後,V下一個結點指向第一P結點之間的鏈接。而將V的下一個結點指向新生成的P節點,這下就變成了:V(next)——>P(next)(第二次新生成的結點)——>P(next)(第一次生成的結點)——>NULL,以次內推,一個鏈表就建立了,其實這個過程是從表尾開始向表頭建立鏈表的,所以打印輸出的時候是反過來的。
注意:很多人可能在寫鏈表的創建,插入和刪除的時候,會出現這樣的錯誤,定義一個指針類型的鏈表變量(如:LinkList *List),然後把這個變量傳遞給函數的形參(插入和刪除的函數不再main()所在的同意文件裏),然後函數的返回值爲void,在創建好鏈表後,依然把List傳遞給插入和刪除的函數,或者在main()裏面打印輸出鏈表值,這時編譯不會出錯,運行就出錯了,很多人認爲我傳遞的是指針變量,是地址啊,怎麼在main()裏面不能用呢,至於怎麼回事,自己看一下指針方面的知識,我用了一段時間的java,c有點忘了。解決辦法就是把建好的鏈表返回來就可以了。
下面是java的鏈表實現程序:
import java.util.Scanner;
public class LinkList{
private int num = 0;
private class Node{
String elem;
Node next;
public Node(){
this.elem = null;
this.next = null;
}
}
private Node getNode(){
return new Node();
}
private Node LinkListInit(){
System.out.println("初始化線性鏈表");
Node root = new Node();//構造頭節點
root.elem = "根節點";
for(int i = 0 ; i < 10 ; i++){
Node node = new Node();
node.elem = "第" + i + "節點";
node.next = root.next;
root.next = node;
root.elem = "一共" + i + "節點";
}
return root;
}
private void outPut(Node root){
for(int i = 0 ; i < 10 ; i++){
if(root != null){
System.out.println(root.elem);
root = root.next;
}
}
}
private Node deleteLinkList(Node root){
Node ret = new Node();
ret = root;
Scanner scanner = new Scanner(System.in);// 創建輸入流掃描器
System.out.println("請輸入需要刪除元素的位置:");// 提示用戶輸入
int space = scanner.nextInt();// 獲取用戶輸入的一行文本
if(ret != null) {
for(int i = 1 ; i < space ; i++){
if(ret == null){
System.out.println("你輸入的位置不正確:");
return root;
}
ret = ret.next;
}
}
else{
System.out.println("請輸入的位置不正確:");
}
ret.next = (ret.next).next;
num++;
root.elem = "一共" + (9 - num) + "節點" ;
return root;
}
public static void main(String[] args){
LinkList link = new LinkList();
Node root = link.getNode();
root = link.LinkListInit();//初始化鏈表
link.outPut(root);//打印出每個節點值
root = link.deleteLinkList(root);
link.outPut(root);//打印出每個節點值
}
}
靜態鏈表的表示:用數組描述的鏈表,需要預先分配一定的空間。但解決了數據插入和刪除是需要的移動元素的缺點,靜態鏈表插入和刪除數據是不需要移動數據元素。好像數組裏面有了鏈表的特性。
那爲什麼要用靜態鏈表呢,不是有鏈表嗎?鏈表是嚴重依賴指針的,在沒有指針語法的語言裏面那我們該怎麼實現類似鏈表的功能呢?那就要用靜態鏈表了。
靜態鏈表的使用思想是怎麼樣的呢?靜態鏈表裏。我們把數組的每一個分量(也就是數據項)表示一個結點,裏面存儲數值和一個下標數值,這個下標數值就像鏈表裏的*next一樣,存儲的值 = 它所指向的那個數據的在數組中的位置。例如下面的:(下標爲0的定義位頭節點,指向0表示數組結束)。[圖片上傳失敗...(image-bdb8df-1545565392987)]
它的邏輯結構是這樣的:
那麼我們插入數據是可以將數據插入任意空的地址空間,然後想鏈表那樣修改對應的下標值就是了,也不用移動數據:程序如下:
StaticList* StaticList_Create(int capacity) // O(n)
{
TStaticList* ret = NULL;
int i = 0;
if( capacity >= 0 )
{
ret = (TStaticList*)malloc(sizeof(TStaticList) + sizeof(TStaticListNode) * (capacity + 1));
}
if( ret != NULL )
{
ret->capacity = capacity;
ret->header.data = 0;
ret->header.next = 0;
for(i=1; i<=capacity; i++)
{
ret->node[i].next = AVAILABLE;
}
}
return ret;
}
int StaticList_Insert(StaticList* list, StaticListNode* node, int pos) // O(n)
{
TStaticList* sList = (TStaticList*)list;
int ret = (sList != NULL);
int current = 0;
int index = 0;
int i = 0;
ret = ret && (sList->header.data + 1 <= sList->capacity);
ret = ret && (pos >=0) && (node != NULL);
if( ret )
{
for(i=1; i<=sList->capacity; i++)
{
if( sList->node[i].next == AVAILABLE )
{
index = i;
break;
}
}
sList->node[index].data = (unsigned int)node;
sList->node[0] = sList->header;
for(i=0; (i<pos) && (sList->node[current].next != 0); i++)
{
current = sList->node[current].next;
}
sList->node[index].next = sList->node[current].next;
sList->node[current].next = index;
sList->node[0].data++;
sList->header = sList->node[0];
}
return ret;
}
循環鏈表:這個在前面的基礎上增加指針就可以實現,這裏就不講了。
總結:爲什麼用線性表順序存儲:方便隨機存取,但是插入和刪除需要大量移動數據,能夠繼續增加存儲空間大小,不能動態生成,因爲每次分配內存時內存不一定是連續的,而順序表不能通過指針指向下一個元素,所以不能動態生成。
問什麼用鏈表:能動態生成,不需要移動元素,存儲靈活等
爲什麼用靜態鏈表:在不支持指針的語言中也能實現鏈表的存儲和操作等
工程地址:http://download.csdn.net/detail/tuoguang/9164793
打開工具dev-c++: