qsort函数在C意外行为?

时间:2013-11-18 22:02:28

标签: c arrays sorting qsort

我遇到了 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开始。 STEP ,如下所示:

列表:[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. STEP:
  2. 擦除(分钟)节点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. STEP:
    2. 擦除(分钟)节点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. STEP:
      2. 擦除(分钟)节点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. STEP:
        2. 擦除(分钟)节点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. STEP:
          2. 擦除(分钟)节点0.100000 ...

            擦除(分钟)节点0.150000 ...

            创建(新)节点0.250000 ...

            列表:[0.15 | 999] [0.15 | 1] [0.20 | 1] [0.25 | 1] [0.25 | 999]

            1. STEP:
            2. 擦除(分钟)节点0.150000 ...

              擦除(分钟)节点0.150000 ...

              删除(新)节点0.300000 ...

              列表:[0.20 | 1] [0.25 | 1] [0.25 | 999] [0.30 | 1]

              1. STEP:
              2. 擦除(分钟)节点0.200000 ...

                擦除(分钟)节点0.250000 ...

                擦除(新)节点0.450000 ...

                列表:[0.25 | 999] [0.30 | 1] [0.45 | 1]

                1. STEP:
                2. 擦除(分钟)节点0.250000 ...

                  擦除(分钟)节点0.300000 ...

                  创建(新)节点0.550000 ...

                  列表:[0.45 | 1] [0.55 | 1]

                  1. STEP:
                  2. 擦除(分钟)节点0.450000 ...

                    擦除(分钟)节点0.550000 ...

                    创建(新)节点1.000000 ...

                    列表:[1.00 | 1]

                    一般想法 *

                    在每个步骤中,从列表中删除两个最小节点,并将一个新节点插入到列表中。插入的节点的值 t 的值比列表中的最大值大1,除了它不会将自身与 t 值999进行比较。如果列表中最大的 t = 999,然后插入的那个将有1。

                    找到最好的

                    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));
                     }
                    

1 个答案:

答案 0 :(得分:2)

您遇到的问题是浮点不精确。精确的十进制数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),您可以使用小数定点表示(例如,将概率乘以long int中的10000乘以而不是{{1} },然后除以10000除以显示)。