簡體   English   中英

qsort函數在C意外行為?

[英]qsort function in C unexpected behaviour?

我遇到了qsort()函數的奇怪和意外行為。 我有我的節點列表,每個節點包含兩個值,我想根據這些值對列表進行排序(技術上,數組)。

例如:

如果我有一個看起來像這樣的原始數組(第一個元素是p ,第二個元素是t ):

[0,10 | 1],[0.05 | 0],[0,10 | 0],[0,05 | 2],[0,10 | 2],[0,15 | 1],[0,05 | 1]

排序后,它應如下所示:

[0,05 | 0],[0,05 | 1],[0,05 | 2],[0,10 | 0],[0,10 | 1],[0,10 | 2],[0 ,15 | 1]。

結構節點:

typedef struct node{
    int t;
    double p;
}node;

比較函數 ,稱為qsort(nodes, num_of_node, sizeof(node), compare_pairs);

static int compare_pairs(const void *n1, const void *n2){

const node *na1= n1;
const node *na2= n2;

if(na1->p < na2->p) return -1;
if(na1->p > na2->p) return 1;

// At this point, value p is equal, so I am sorting based on t

if(na1->t < na2->t) return -1;
if(na1->t > na2->t) return 1;

return 0;

問題

不需要的行為從3.步驟開始,如下所示:

清單:[0.10 | 2] [0.10 | 999] [0.10 | 999] [0.15 | 999] [0.15 | 999] [0.15 | 1] [0.25 | 999]

應該是這樣的:

清單:[0.10 | 2] [0.10 | 999] [0.10 | 999] [0.15 | 1] [0.15 | 999] [0.15 | 999] [0.25 | 999]

初始清單:[0.25 | 999] [0.15 | 999] [0.15 | 999] [0.10 | 999] [0.10 | 999] [0.05 | 999] [0.05 | 999] [0.05 | 999] [0.05 | 999] [0.05 | 999]

  1. 步:

擦除(分鍾)節點0.050000 ...

擦除(分鍾)節點0.050000 ...

創建(新)節點0.100000 ...

清單:[0.05 | 999] [0.05 | 999] [0.05 | 999] [0.10 | 1] [0.10 | 999] [0.10 | 999] [0.15 | 999] [0.15 | 999] [0.25 | 999]

  1. 步:

擦除(分鍾)節點0.050000 ...

擦除(分鍾)節點0.050000 ...

創建(新)節點0.100000 ...

清單:[0.05 | 999] [0.10 | 1] [0.10 | 2] [0.10 | 999] [0.10 | 999] [0.15 | 999] [0.15 | 999] [0.25 | 999]

  1. 步:

擦除(分鍾)節點0.050000 ...

擦除(最小)節點0.100000 ...

創建(新)節點0.150000 ...

清單:[0.10 | 2] [0.10 | 999] [0.10 | 999] [0.15 | 999] [0.15 | 999] [0.15 | 1] [0.25 | 999]

  1. 步:

擦除(最小)節點0.100000 ...

擦除(最小)節點0.100000 ...

擦除(新)節點0.200000 ...

清單:[0.10 | 999] [0.15 | 999] [0.15 | 999] [0.15 | 1] [0.20 | 1] [0.25 | 999]

  1. 步:

擦除(最小)節點0.100000 ...

擦除(分鍾)節點0.150000 ...

創建(新)節點0.250000 ...

清單:[0.15 | 999] [0.15 | 1] [0.20 | 1] [0.25 | 1] [0.25 | 999]

  1. 步:

擦除(分鍾)節點0.150000 ...

擦除(分鍾)節點0.150000 ...

擦除(新)節點0.300000 ...

清單:[0.20 | 1] [0.25 | 1] [0.25 | 999] [0.30 | 1]

  1. 步:

擦除(分鍾)節點0.200000 ...

擦除(最小)節點0.250000 ...

擦除(新)節點0.450000 ...

清單:[0.25 | 999] [0.30 | 1] [0.45 | 1]

  1. 步:

擦除(最小)節點0.250000 ...

擦除(分鍾)節點0.300000 ...

創建(新)節點0.550000 ...

清單:[0.45 | 1] [0.55 | 1]

  1. 步:

擦除(分鍾)節點0.450000 ...

擦除(分鍾)節點0.550000 ...

創建(新)節點1.000000 ...

清單:[1.00 | 1]

總體思路 *

在每個步驟中,從列表中刪除兩個最小節點,並將一個新節點插入到列表中。 插入的節點的值為t ,大於列表中的最大值,但它不會將自身與t值999進行比較。如果列表中的最大值為t = 999,則插入的節點將為有1。

找到最大的t

int max_t(node *nodes, int num, double p){
int max_t= 0;
int i;
for(i=0; i<num; i+=1){
    if(nodes[i].p== p && nodes[i].t != 999){
        if(nodes[i].t > max_t){
            max_t = nodes[i].t;
        }
    }
}
return max_t;

主要代碼:

node *nodes = malloc(num_of_nodes*sizeof(node));
int i;
for(i=0; i<num_of_nodes; i+=1){
    node n;
    n.t = 999;
    n.p = *(probabs+ i);
    *(nodes+i) = n;
}

qsort(nodes, num_of_nodes, sizeof(node), compare_pairs);

while(num_of_nodes> 1){

    printf("\n%d. STEP:\n", z);
    z += 1;

    // 2) Find two min nodes
    node *min_n1 = malloc(sizeof(node));
    node *min_n2 = malloc(sizeof(node));

    *min_n1 = nodes[0];
    printf("Erasing (min) node %lf...\n", min_n1->p);
    nodes= erase_node(nodes, min_n1, num_of_nodes);
    num_of_nodes -= 1;

    *min_n2 = nodes[0];
    printf("Erasing (min) node %lf...\n", min_n2->p);
    nodes= erase_node(nodes, min_n2, num_of_nodes);
    num_of_nodes-= 1;

    // 3) Create new node, add it to the list
    node *new_node = malloc(sizeof(node));
    new_node->p= min_n1->p + min_n2->p;
    double p = new_node->p;
    int max_t = max_t(nodes, num_of_nodes, p);
    new_node->t = max_t + 1;

    printf("Creating (new) node %lf...\n", new_node->p);
    nodes = add_node(nodes, new_node, num_of_nodes);
    num_of_nodes += 1;

    qsort(nodes, num_of_nodes, sizeof(node), compare_pairs);

    printf("List: ");
    int k;
    for(k=0; k<num_of_nodes; k+=1){
        printf("[%.2lf | %d]  ", nodes[k].p, nodes[k].t);
    }
    printf("\n");

添加/刪除節點...

node *add_node(node *nodes, node *n, int num){
nodes = realloc(nodes, (num+1)*sizeof(node));
nodes[num] = *n;
return nodes;

node *erase_node(node *nodes, node *n, int num){
int i;
int index = 0;
for(i=0; i<num; i+=1){
    if(nodes_equal(&nodes[i], n)){
        index = i;
        break;
    }
}

for(i=index; i<num-1; i+=1){
    nodes[i] = nodes[i+1];
}

nodes= realloc(nodes, (num-1)*sizeof(node));

return nodes;

}

   int nodes_equal(node *n1, node *n2){

    return !memcmp(n1, n2, sizeof(node));
 }

你遇到的問題是浮點不精確。 精確的十進制數0.1,0.05和0.15都沒有二進制浮點的精確表示。

使用IEEE 64位double表示,最接近的可表示值0.15略小於0.15,並且最接近的可表示值0.05和0.10分別略大於0.05和0.10。 使用舍入到最接近的實現,這意味着如果你加起來0.05和0.10,你最終會得到一個略大於0.15的數字,如果你直接設置一個double到0.15,你最終會得到一個稍微少一點的數字比0.15。 這些不會相等。

這是您在步驟3中顯然發生的事情。您刪除了值為0.05和0.10的兩個節點(實際上,正如所討論的那樣,它們的實際值略大於這些數字)並將它們加在一起,最后得到的數字略大於0.15 。 這比實際值略小於 0.15的現有節點要大,因此在它們之后進行排序。

不過,目前尚不清楚這對你的算法是否真的重要嗎? 最終它不會以同樣的最終狀態結束嗎? 如果它確實很重要,因為你顯然存儲的概率范圍從0.0到1.0(包括0.0和1.0),你可以使用小數點定點表示(例如,將概率乘以10000乘以long int而不是double ,然后除以10000用於顯示)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM